Move to FrogPilot API
This commit is contained in:
+19
-16
@@ -76,10 +76,11 @@ struct FrogPilotCarState @0xf35cc4560bbf6ec2 {
|
||||
distanceVeryLongPressed @7 :Bool;
|
||||
ecoGear @8 :Bool;
|
||||
forceCoast @9 :Bool;
|
||||
pauseLateral @10 :Bool;
|
||||
pauseLongitudinal @11 :Bool;
|
||||
sportGear @12 :Bool;
|
||||
trafficModeEnabled @13 :Bool;
|
||||
isParked @10 :Bool;
|
||||
pauseLateral @11 :Bool;
|
||||
pauseLongitudinal @12 :Bool;
|
||||
sportGear @13 :Bool;
|
||||
trafficModeEnabled @14 :Bool;
|
||||
}
|
||||
|
||||
struct FrogPilotDeviceState @0xda96579883444c35 {
|
||||
@@ -108,8 +109,8 @@ struct FrogPilotOnroadEvent @0xa5cd762cd951a455 {
|
||||
immediateDisable @6 :Bool;
|
||||
preEnable @7 :Bool;
|
||||
permanent @8 :Bool;
|
||||
overrideLateral @10 :Bool;
|
||||
overrideLongitudinal @9 :Bool;
|
||||
overrideLateral @9 :Bool;
|
||||
overrideLongitudinal @10 :Bool;
|
||||
|
||||
enum EventName {
|
||||
blockUser @0;
|
||||
@@ -194,16 +195,18 @@ struct FrogPilotRadarState @0xb86e6369214c01c8 {
|
||||
vRel @2 :Float32;
|
||||
aRel @3 :Float32;
|
||||
vLead @4 :Float32;
|
||||
dPath @5 :Float32;
|
||||
vLat @6 :Float32;
|
||||
vLeadK @7 :Float32;
|
||||
aLeadK @8 :Float32;
|
||||
fcw @9 :Bool;
|
||||
status @10 :Bool;
|
||||
aLeadTau @11 :Float32;
|
||||
modelProb @12 :Float32;
|
||||
radar @13 :Bool;
|
||||
radarTrackId @14 :Int32 = -1;
|
||||
dPath @6 :Float32;
|
||||
vLat @7 :Float32;
|
||||
vLeadK @8 :Float32;
|
||||
aLeadK @9 :Float32;
|
||||
fcw @10 :Bool;
|
||||
status @11 :Bool;
|
||||
aLeadTau @12 :Float32;
|
||||
modelProb @13 :Float32;
|
||||
radar @14 :Bool;
|
||||
radarTrackId @15 :Int32 = -1;
|
||||
|
||||
aLeadDEPRECATED @5 :Float32;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"AvailableModelNames", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"AvailableModels", {PERSISTENT, STRING, "", "", 1}},
|
||||
{"BlacklistedModels", {PERSISTENT, STRING, "", "", 2}},
|
||||
{"BuildMetadata", {PERSISTENT, STRING, "", "", 0}},
|
||||
{"BlindSpotMetrics", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"BlindSpotPath", {PERSISTENT, BOOL, "1", "0", 1}},
|
||||
{"BorderMetrics", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
@@ -240,6 +241,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ForceStops", {PERSISTENT, BOOL, "0", "0", 2}},
|
||||
{"ForceTorqueController", {PERSISTENT, BOOL, "0", "0", 3}},
|
||||
{"FPSCounter", {PERSISTENT, BOOL, "1", "0", 3}},
|
||||
{"FrogPilotApiToken", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
{"FrogPilotCarParams", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BYTES, "", ""}},
|
||||
{"FrogPilotCarParamsPersistent", {PERSISTENT, BYTES, "", ""}},
|
||||
{"FrogPilotDongleId", {PERSISTENT | DONT_LOG, STRING, "", "", 0}},
|
||||
@@ -335,6 +337,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"PauseLateralOnSignal", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PauseLateralSpeed", {PERSISTENT, FLOAT, "0.0", "0.0", 1}},
|
||||
{"PedalsOnUI", {PERSISTENT, BOOL, "0", "0", 1}},
|
||||
{"PondPaired", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PondUploadPending", {PERSISTENT, BOOL, "0", "0", 0}},
|
||||
{"PreferredSchedule", {PERSISTENT, INT, "2", "0", 0}},
|
||||
{"PreviousSpeedLimit", {PERSISTENT, FLOAT, "0.0", "0.0"}},
|
||||
{"PromptDistractedVolume", {PERSISTENT, INT, "101", "101", 2}},
|
||||
|
||||
@@ -51,9 +51,12 @@ def backup_toggles(params, boot_run=False):
|
||||
cleanup_backups(TOGGLE_BACKUPS, maximum_backups)
|
||||
|
||||
if not changes_found or boot_run:
|
||||
print("Toggles are identical to the previous backup. Aborting...")
|
||||
if not changes_found:
|
||||
print("Toggles are identical to the previous backup. Aborting...")
|
||||
return
|
||||
|
||||
params.put_bool("PondUploadPending", True)
|
||||
|
||||
destination = TOGGLE_BACKUPS / f"{datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}_auto"
|
||||
create_backup(Path(params_backup.get_param_path()), destination, "Successfully backed up toggles!", "Failed to backup toggles...", params)
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import io
|
||||
import dataclasses
|
||||
import json
|
||||
import random
|
||||
import requests
|
||||
import string
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -15,53 +13,45 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.system.athena.registration import register
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
from openpilot.frogpilot.assets.theme_manager import ThemeManager
|
||||
from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd, use_konik_server
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import get_frogpilot_api_info, is_FrogsGoMoo, is_url_pingable, run_cmd, use_konik_server
|
||||
from openpilot.frogpilot.common.frogpilot_variables import (
|
||||
DISCORD_WEBHOOK_URL_REPORT, ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPS_PATH, THEME_SAVE_PATH,
|
||||
ERROR_LOGS_PATH, FROGPILOT_API, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, MAPS_PATH, THEME_SAVE_PATH,
|
||||
FrogPilotVariables, get_frogpilot_toggles
|
||||
)
|
||||
|
||||
|
||||
def capture_report(discord_user, report, frogpilot_toggles):
|
||||
if not DISCORD_WEBHOOK_URL_REPORT:
|
||||
def capture_report(discord_user, report, params, frogpilot_toggles):
|
||||
if not is_url_pingable(FROGPILOT_API):
|
||||
return
|
||||
|
||||
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
|
||||
|
||||
error_file_path = ERROR_LOGS_PATH / "error.txt"
|
||||
error_content = "No error log found."
|
||||
if error_file_path.exists():
|
||||
error_content = error_file_path.read_text()[:1000]
|
||||
|
||||
message = (
|
||||
f"**🚨 New Error Report**\n\n"
|
||||
f"**User:** `{discord_user}`\n\n"
|
||||
f"**Report:**\n```{report}```\n\n"
|
||||
f"**Error Log:**\n```{error_content}```\n\n"
|
||||
f"**Toggle Settings:**"
|
||||
)
|
||||
payload = {
|
||||
"api_token": api_token,
|
||||
"build_metadata": build_metadata,
|
||||
"device": device_type,
|
||||
"discord_user": discord_user,
|
||||
"error_content": error_content,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
"frogpilot_toggles": frogpilot_toggles,
|
||||
"report": report,
|
||||
}
|
||||
|
||||
try:
|
||||
main_response = requests.post(
|
||||
DISCORD_WEBHOOK_URL_REPORT,
|
||||
data={"content": message},
|
||||
files={"file": ("frogpilot_toggles.json", io.BytesIO(json.dumps(frogpilot_toggles, indent=2).encode("utf-8")), "application/json")},
|
||||
timeout=10
|
||||
)
|
||||
main_response.raise_for_status()
|
||||
|
||||
mention_response = requests.post(
|
||||
DISCORD_WEBHOOK_URL_REPORT,
|
||||
json={"content": "<@&1198482895342411846>"},
|
||||
timeout=10
|
||||
)
|
||||
mention_response.raise_for_status()
|
||||
|
||||
response = requests.post(f"{FROGPILOT_API}/discord/report", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=30)
|
||||
response.raise_for_status()
|
||||
print("Successfully sent error report!")
|
||||
except requests.exceptions.RequestException as exception:
|
||||
print(f"Error sending Discord message: {exception}")
|
||||
except Exception as exception:
|
||||
print(f"Unexpected error: {exception}")
|
||||
print(f"Error sending report: {exception}")
|
||||
|
||||
|
||||
def frogpilot_boot_functions(build_metadata, params):
|
||||
@@ -82,6 +72,8 @@ def frogpilot_boot_functions(build_metadata, params):
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
params.put("BuildMetadata", json.dumps(dataclasses.asdict(build_metadata)))
|
||||
|
||||
FrogPilotVariables()
|
||||
ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), frogpilot_toggles=get_frogpilot_toggles(), boot_run=True)
|
||||
|
||||
@@ -114,8 +106,7 @@ def install_frogpilot(build_metadata, params):
|
||||
for path in paths:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if params.get("FrogPilotDongleId") is None:
|
||||
params.put("FrogPilotDongleId", "".join(random.choices(string.ascii_lowercase + string.digits, k=16)))
|
||||
register_device(build_metadata, params)
|
||||
|
||||
update_boot_logo(frogpilot=True)
|
||||
|
||||
@@ -126,6 +117,32 @@ def install_frogpilot(build_metadata, params):
|
||||
run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/persist"], "Successfully restored /persist mount options", "Failed to restore /persist mount options")
|
||||
|
||||
|
||||
def register_device(build_metadata, params):
|
||||
def register_thread():
|
||||
while not is_url_pingable(FROGPILOT_API):
|
||||
time.sleep(60)
|
||||
|
||||
payload = {
|
||||
"api_token": params.get("FrogPilotApiToken"),
|
||||
"build_metadata": dataclasses.asdict(build_metadata),
|
||||
"device": HARDWARE.get_device_type(),
|
||||
"dongle_id": params.get("DongleId"),
|
||||
"frogpilot_dongle_id": params.get("FrogPilotDongleId"),
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(f"{FROGPILOT_API}/register", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
params.put("FrogPilotApiToken", data.get("api_token", ""))
|
||||
params.put("FrogPilotDongleId", data.get("frogpilot_dongle_id"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
threading.Thread(target=register_thread, daemon=True).start()
|
||||
|
||||
|
||||
def uninstall_frogpilot():
|
||||
update_boot_logo(stock=True)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import dataclasses
|
||||
import json
|
||||
import math
|
||||
import numpy as np
|
||||
@@ -17,10 +18,13 @@ import openpilot.system.sentry as sentry
|
||||
from cereal import log, messaging
|
||||
from opendbc.can.parser import CANParser
|
||||
from opendbc.car.toyota.carcontroller import LOCK_CMD
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_DMON, DT_HW
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.version import get_build_metadata
|
||||
from panda import Panda
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGS_GO_MOO_PATH, KONIK_PATH
|
||||
from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGPILOT_API, FROGS_GO_MOO_PATH, KONIK_PATH
|
||||
|
||||
class ThreadManager:
|
||||
def __init__(self):
|
||||
@@ -166,11 +170,40 @@ def flash_panda(params_memory):
|
||||
params_memory.remove("FlashPanda")
|
||||
|
||||
|
||||
def get_frogpilot_api_info():
|
||||
params = Params()
|
||||
|
||||
api_token = params.get("FrogPilotApiToken")
|
||||
build_metadata = dataclasses.asdict(get_build_metadata())
|
||||
device_type = HARDWARE.get_device_type()
|
||||
dongle_id = params.get("FrogPilotDongleId")
|
||||
|
||||
return api_token, build_metadata, device_type, dongle_id
|
||||
|
||||
|
||||
def get_lock_status(can_parser, can_sock):
|
||||
update_can_parser(can_parser, can_sock)
|
||||
return can_parser.vl["DOOR_LOCKS"]["LOCK_STATUS"]
|
||||
|
||||
|
||||
def get_sentry_dsn():
|
||||
try:
|
||||
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
|
||||
|
||||
payload = {
|
||||
"api_token": api_token,
|
||||
"build_metadata": build_metadata,
|
||||
"device": device_type,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
}
|
||||
|
||||
response = requests.post(f"{FROGPILOT_API}/sentry", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json().get("dsn", "")
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
@cache
|
||||
def is_FrogsGoMoo():
|
||||
return FROGS_GO_MOO_PATH.is_file()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import tomllib
|
||||
|
||||
@@ -42,8 +41,7 @@ THRESHOLD = 1 - 1 / math.e # Requires the condition to be true fo
|
||||
|
||||
NON_DRIVING_GEARS = [GearShifter.neutral, GearShifter.park, GearShifter.reverse, GearShifter.unknown]
|
||||
|
||||
DISCORD_WEBHOOK_URL_REPORT = os.getenv("DISCORD_WEBHOOK_URL_REPORT")
|
||||
DISCORD_WEBHOOK_URL_THEME = os.getenv("DISCORD_WEBHOOK_URL_THEME")
|
||||
FROGPILOT_API = "https://frogpilot.com/api"
|
||||
|
||||
RESOURCES_REPO = "FrogAi/FrogPilot-Resources"
|
||||
|
||||
@@ -238,7 +236,7 @@ class FrogPilotVariables:
|
||||
self.frogs_go_moo = FROGS_GO_MOO_PATH.is_file()
|
||||
toggle.block_user = (self.development_branch or branch == "MAKE-PRS-HERE" or self.vetting_branch) and not self.frogs_go_moo
|
||||
|
||||
self.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
|
||||
device_management = self.get_value("DeviceManagement")
|
||||
|
||||
@@ -280,7 +278,7 @@ class FrogPilotVariables:
|
||||
return "#FFFFFFFF"
|
||||
|
||||
def get_value(self, key, cast=bool, condition=True, conversion=None, default=None, min=None, max=None):
|
||||
if not condition or (self.tuning_level < self.tuning_levels.get(key, 0)):
|
||||
if not condition or (self.frogpilot_toggles.tuning_level < self.tuning_levels.get(key, 0)):
|
||||
if default is not None:
|
||||
return default
|
||||
return False if cast is bool else self.default_values.get(key)
|
||||
@@ -311,7 +309,7 @@ class FrogPilotVariables:
|
||||
|
||||
def update(self, holiday_theme="stock", started=False):
|
||||
toggle = self.frogpilot_toggles
|
||||
self.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
toggle.tuning_level = self.params.get("TuningLevel") if self.params.get_bool("TuningLevelConfirmed") else TUNING_LEVELS["ADVANCED"]
|
||||
|
||||
msg_bytes = self.params.get("CarParams" if started else "CarParamsPersistent", block=started)
|
||||
if msg_bytes:
|
||||
|
||||
@@ -5,7 +5,7 @@ from openpilot.selfdrive.car.cruise import CRUISE_LONG_PRESS, ButtonType
|
||||
from openpilot.selfdrive.selfdrived.events import ET
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo
|
||||
from openpilot.frogpilot.common.frogpilot_variables import ERROR_LOGS_PATH, NON_DRIVING_GEARS
|
||||
from openpilot.frogpilot.common.frogpilot_variables import ERROR_LOGS_PATH, GearShifter, NON_DRIVING_GEARS
|
||||
from openpilot.frogpilot.controls.lib.conditional_experimental_mode import CEStatus
|
||||
|
||||
class FrogPilotCard:
|
||||
@@ -109,6 +109,7 @@ class FrogPilotCard:
|
||||
frogpilotCarState.distanceLongPressed = self.very_long_press_threshold > self.gap_counter >= self.long_press_threshold
|
||||
frogpilotCarState.distanceVeryLongPressed = self.gap_counter >= self.very_long_press_threshold
|
||||
frogpilotCarState.forceCoast = self.force_coast
|
||||
frogpilotCarState.isParked = carState.gearShifter == GearShifter.park
|
||||
frogpilotCarState.pauseLateral = self.pause_lateral
|
||||
frogpilotCarState.pauseLongitudinal = self.pause_longitudinal
|
||||
frogpilotCarState.trafficModeEnabled = self.traffic_mode_enabled
|
||||
|
||||
@@ -33,6 +33,7 @@ class FrogPilotPlanner:
|
||||
self.frogpilot_weather = WeatherChecker(self)
|
||||
|
||||
self.driving_in_curve = False
|
||||
self.gps_valid = False
|
||||
self.lateral_check = False
|
||||
self.model_stopped = False
|
||||
self.road_curvature_detected = False
|
||||
@@ -84,6 +85,7 @@ class FrogPilotPlanner:
|
||||
"longitude": gps_location.longitude,
|
||||
"bearing": gps_location.bearingDeg,
|
||||
}
|
||||
self.gps_valid = self.gps_position["latitude"] != 0 or self.gps_position["longitude"] != 0
|
||||
self.params_memory.put("LastGPSPosition", json.dumps(self.gps_position))
|
||||
|
||||
if v_ego >= frogpilot_toggles.minimum_lane_change_speed:
|
||||
@@ -113,7 +115,7 @@ class FrogPilotPlanner:
|
||||
|
||||
self.v_cruise = self.frogpilot_vcruise.update(long_control_active, now, time_validated, v_cruise, v_ego, sm, frogpilot_toggles)
|
||||
|
||||
if self.gps_position and time_validated and frogpilot_toggles.weather_presets:
|
||||
if self.gps_valid and time_validated and frogpilot_toggles.weather_presets:
|
||||
self.frogpilot_weather.update_weather(now, frogpilot_toggles)
|
||||
else:
|
||||
self.frogpilot_weather.weather_id = 0
|
||||
|
||||
@@ -78,7 +78,7 @@ class SpeedLimitController:
|
||||
return next((getattr(self.frogpilot_toggles, offset) for low, high, offset in offset_map if low < self.target < high), 0)
|
||||
|
||||
def get_mapbox_speed_limit(self, now, time_validated, v_ego, sm):
|
||||
if not self.frogpilot_planner.gps_position or not self.mapbox_token or (sm["carState"].steeringAngleDeg - sm["liveParameters"].angleOffsetDeg) >= 45:
|
||||
if not self.frogpilot_planner.gps_valid or not self.mapbox_token or (sm["carState"].steeringAngleDeg - sm["liveParameters"].angleOffsetDeg) >= 45:
|
||||
self.mapbox_limit = 0
|
||||
self.segment_distance = 0
|
||||
return
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import calculate_distance_to_point, is_url_pingable
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import calculate_distance_to_point, get_frogpilot_api_info, is_url_pingable
|
||||
from openpilot.frogpilot.common.frogpilot_variables import FROGPILOT_API
|
||||
|
||||
CACHE_DISTANCE = 25
|
||||
MAX_RETRIES = 3
|
||||
@@ -54,18 +54,19 @@ class WeatherChecker:
|
||||
self.hourly_forecast = None
|
||||
self.last_gps_position = None
|
||||
self.last_updated = None
|
||||
self.requesting = False
|
||||
|
||||
user_api_key = self.frogpilot_planner.params.get("WeatherToken")
|
||||
self.api_key = user_api_key or os.environ.get("WEATHER_TOKEN", "")
|
||||
self.user_api_key = self.frogpilot_planner.params.get("WeatherToken")
|
||||
|
||||
if user_api_key:
|
||||
if self.user_api_key:
|
||||
self.check_interval = 60
|
||||
else:
|
||||
self.check_interval = 15 * 60
|
||||
|
||||
self.api_token, self.build_metadata, self.device_type, self.dongle_id = get_frogpilot_api_info()
|
||||
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({"Accept-Language": "en"})
|
||||
self.session.headers.update({"User-Agent": "frogpilot-weather-checker/1.0 (https://github.com/FrogAi/FrogPilot)"})
|
||||
self.session.headers.update({"Accept-Language": "en", "User-Agent": "frogpilot-api/1.0"})
|
||||
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
|
||||
@@ -88,10 +89,6 @@ class WeatherChecker:
|
||||
self.reduce_lateral_acceleration = 0
|
||||
|
||||
def update_weather(self, now, frogpilot_toggles):
|
||||
if not self.api_key:
|
||||
self.weather_id = 0
|
||||
return
|
||||
|
||||
if self.last_gps_position and self.last_updated:
|
||||
distance = calculate_distance_to_point(
|
||||
self.last_gps_position["latitude"],
|
||||
@@ -113,11 +110,16 @@ class WeatherChecker:
|
||||
self.update_offsets(frogpilot_toggles)
|
||||
return
|
||||
|
||||
self.last_updated = now
|
||||
if self.requesting:
|
||||
return
|
||||
|
||||
self.requesting = True
|
||||
|
||||
def complete_request(future):
|
||||
self.requesting = False
|
||||
data = future.result()
|
||||
if data:
|
||||
self.last_updated = now
|
||||
self.hourly_forecast = data.get("hourly")
|
||||
self.last_gps_position = self.frogpilot_planner.gps_position
|
||||
|
||||
@@ -135,32 +137,30 @@ class WeatherChecker:
|
||||
self.update_offsets(frogpilot_toggles)
|
||||
|
||||
def make_request():
|
||||
if not is_url_pingable("https://api.openweathermap.org"):
|
||||
if not is_url_pingable(FROGPILOT_API):
|
||||
return None
|
||||
|
||||
params = {
|
||||
payload = {
|
||||
"api_key": self.user_api_key,
|
||||
"api_token": self.api_token,
|
||||
"build_metadata": self.build_metadata,
|
||||
"device": self.device_type,
|
||||
"frogpilot_dongle_id": self.dongle_id,
|
||||
"lat": self.frogpilot_planner.gps_position["latitude"],
|
||||
"lon": self.frogpilot_planner.gps_position["longitude"],
|
||||
"appid": self.api_key,
|
||||
"units": "metric",
|
||||
"exclude": "alerts,minutely,daily",
|
||||
}
|
||||
|
||||
for attempt in range(1, MAX_RETRIES + 1):
|
||||
try:
|
||||
self.api_3_calls += 1
|
||||
response = self.session.get("https://api.openweathermap.org/data/3.0/onecall", params=params, timeout=10)
|
||||
|
||||
if response.status_code in (401, 403, 429):
|
||||
fallback_params = params.copy()
|
||||
fallback_params.pop("exclude", None)
|
||||
self.api_25_calls += 1
|
||||
fallback_response = self.session.get("https://api.openweathermap.org/data/2.5/weather", params=fallback_params, timeout=10)
|
||||
fallback_response.raise_for_status()
|
||||
return fallback_response.json()
|
||||
|
||||
response = self.session.post(f"{FROGPILOT_API}/weather", json=payload, headers={"Content-Type": "application/json"}, timeout=10)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
data = response.json()
|
||||
if data.get("api_version") == "2.5":
|
||||
self.api_25_calls += 1
|
||||
else:
|
||||
self.api_3_calls += 1
|
||||
return data
|
||||
except Exception:
|
||||
if attempt < MAX_RETRIES:
|
||||
time.sleep(RETRY_DELAY)
|
||||
|
||||
@@ -30,7 +30,7 @@ def check_assets(now, theme_manager, thread_manager, params, params_memory, frog
|
||||
|
||||
report_data = params_memory.get("IssueReported")
|
||||
if report_data:
|
||||
capture_report(report_data["DiscordUser"], report_data["Issue"], vars(frogpilot_toggles))
|
||||
capture_report(report_data["DiscordUser"], report_data["Issue"], params, vars(frogpilot_toggles))
|
||||
params_memory.remove("IssueReported")
|
||||
|
||||
if params_memory.get_bool("DownloadMaps"):
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
#!/usr/bin/env python3
|
||||
import requests
|
||||
import time
|
||||
|
||||
from cereal import messaging
|
||||
|
||||
from openpilot.common.params import Params, ParamKeyFlag, ParamKeyType
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import get_frogpilot_api_info, is_url_pingable
|
||||
from openpilot.frogpilot.common.frogpilot_variables import EXCLUDED_KEYS, FROGPILOT_API, update_frogpilot_toggles
|
||||
|
||||
POND_PRESENCE_INTERVAL_ACTIVE = 60
|
||||
POND_PRESENCE_INTERVAL_IDLE = 240
|
||||
|
||||
REMOTE_TOGGLE_CHECK_INTERVAL_ACTIVE = 10
|
||||
REMOTE_TOGGLE_CHECK_INTERVAL_IDLE = 60
|
||||
|
||||
|
||||
def check_toggles(started, params, sm=None, boot_run=False):
|
||||
if not params.get_bool("PondPaired"):
|
||||
return None
|
||||
|
||||
if not is_url_pingable(FROGPILOT_API):
|
||||
return None
|
||||
|
||||
if not boot_run:
|
||||
if started and not sm["frogpilotCarState"].isParked:
|
||||
return None
|
||||
if sm["deviceState"].screenBrightnessPercent == 0:
|
||||
return None
|
||||
|
||||
try:
|
||||
api_token, _, device_type, dongle_id = get_frogpilot_api_info()
|
||||
if not dongle_id or not api_token:
|
||||
return None
|
||||
|
||||
response = requests.get(
|
||||
f"{FROGPILOT_API}/pond/toggles/pending",
|
||||
params={"dongle_id": dongle_id, "api_token": api_token},
|
||||
headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"},
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
pond_active = data.get("pond_active") is True
|
||||
|
||||
if data.get("paired") is False:
|
||||
params.put_bool("PondPaired", False)
|
||||
print("Device was unpaired remotely")
|
||||
return False
|
||||
|
||||
toggles = data.get("toggles")
|
||||
if not toggles:
|
||||
return pond_active
|
||||
|
||||
for key, value in toggles.items():
|
||||
if key in EXCLUDED_KEYS:
|
||||
continue
|
||||
try:
|
||||
params.check_key(key)
|
||||
except Exception:
|
||||
print(f"Skipping unknown param key: {key}")
|
||||
continue
|
||||
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
casted_value = params.cpp2python(key, value.encode("utf-8") if isinstance(value, str) else value)
|
||||
if casted_value is not None:
|
||||
params.put(key, casted_value)
|
||||
except Exception as exception:
|
||||
print(f"Skipping remote toggle {key}: {exception}")
|
||||
continue
|
||||
|
||||
update_frogpilot_toggles()
|
||||
|
||||
requests.post(
|
||||
f"{FROGPILOT_API}/pond/toggles/ack",
|
||||
json={
|
||||
"api_token": api_token,
|
||||
"device": device_type,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
},
|
||||
headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"},
|
||||
timeout=10,
|
||||
).raise_for_status()
|
||||
|
||||
print(f"Successfully applied {len(toggles)} remote toggles")
|
||||
return pond_active
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to check remote toggles: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def ping_pond_presence(interval, parked, started, state_changed):
|
||||
last_ping = getattr(ping_pond_presence, "_last_ping", 0.0)
|
||||
now = time.monotonic()
|
||||
if not state_changed and (now - last_ping) < interval:
|
||||
return
|
||||
|
||||
try:
|
||||
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
|
||||
if not dongle_id or not api_token:
|
||||
return
|
||||
|
||||
payload = {
|
||||
"api_token": api_token,
|
||||
"build_metadata": build_metadata,
|
||||
"device": device_type,
|
||||
"dongle_id": dongle_id,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
"is_onroad": bool(started),
|
||||
"is_parked": bool(parked),
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{FROGPILOT_API}/pond/presence/device",
|
||||
json=payload,
|
||||
headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"},
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
ping_pond_presence._last_ping = now
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to update Pond presence: {e}")
|
||||
|
||||
|
||||
def upload_toggles(params):
|
||||
if not is_url_pingable(FROGPILOT_API):
|
||||
return False
|
||||
|
||||
try:
|
||||
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
|
||||
if not dongle_id or not api_token:
|
||||
return False
|
||||
|
||||
toggles = {}
|
||||
for key in params.all_keys():
|
||||
key_str = key.decode("utf-8") if isinstance(key, bytes) else str(key)
|
||||
if key_str in EXCLUDED_KEYS:
|
||||
continue
|
||||
if params.get_key_flag(key) & ParamKeyFlag.DONT_LOG:
|
||||
continue
|
||||
|
||||
value = params.get(key)
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
key_type = params.get_type(key)
|
||||
if key_type == ParamKeyType.BYTES:
|
||||
value = value.decode("utf-8", "replace")
|
||||
elif key_type == ParamKeyType.TIME:
|
||||
value = value.isoformat()
|
||||
toggles[key_str] = value
|
||||
|
||||
payload = {
|
||||
"api_token": api_token,
|
||||
"build_metadata": build_metadata,
|
||||
"device": device_type,
|
||||
"dongle_id": dongle_id,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
"toggles": toggles,
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{FROGPILOT_API}/pond/toggles/sync",
|
||||
json=payload,
|
||||
headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"},
|
||||
timeout=10,
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
print("Successfully uploaded toggles to FrogPilot.com")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to upload toggles: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def pond_thread():
|
||||
rate_keeper = Ratekeeper(1, None)
|
||||
|
||||
sm = messaging.SubMaster(["deviceState", "frogpilotCarState"])
|
||||
|
||||
params = Params(return_defaults=True)
|
||||
|
||||
boot_sync_complete = False
|
||||
pond_active = False
|
||||
previous_parked = False
|
||||
previous_started = False
|
||||
|
||||
next_toggle_check_at = 0.0
|
||||
|
||||
while True:
|
||||
sm.update(0)
|
||||
|
||||
parked = sm["frogpilotCarState"].isParked
|
||||
started = sm["deviceState"].started
|
||||
state_changed = started != previous_started or parked != previous_parked
|
||||
|
||||
if params.get_bool("PondPaired"):
|
||||
presence_interval = POND_PRESENCE_INTERVAL_ACTIVE if started or pond_active else POND_PRESENCE_INTERVAL_IDLE
|
||||
ping_pond_presence(presence_interval, parked, started, state_changed)
|
||||
|
||||
if not boot_sync_complete and system_time_valid():
|
||||
boot_pond_active = check_toggles(False, params, boot_run=True)
|
||||
if boot_pond_active is not None:
|
||||
pond_active = boot_pond_active
|
||||
boot_sync_complete = True
|
||||
|
||||
now = time.monotonic()
|
||||
if state_changed and parked:
|
||||
next_toggle_check_at = 0.0
|
||||
|
||||
if boot_sync_complete and now >= next_toggle_check_at:
|
||||
latest_pond_active = check_toggles(started, params, sm)
|
||||
if latest_pond_active is not None:
|
||||
pond_active = latest_pond_active
|
||||
next_toggle_check_at = now + REMOTE_TOGGLE_CHECK_INTERVAL_ACTIVE if pond_active else REMOTE_TOGGLE_CHECK_INTERVAL_IDLE
|
||||
|
||||
if params.get_bool("PondUploadPending"):
|
||||
if not params.get_bool("PondPaired"):
|
||||
params.put_bool("PondUploadPending", False)
|
||||
elif upload_toggles(params):
|
||||
params.put_bool("PondUploadPending", False)
|
||||
|
||||
previous_parked = parked
|
||||
previous_started = started
|
||||
|
||||
rate_keeper.keep_time()
|
||||
|
||||
|
||||
def main():
|
||||
pond_thread()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,24 +1,11 @@
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import requests
|
||||
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone
|
||||
from influxdb_client import InfluxDBClient, Point
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
|
||||
from cereal import car, custom
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
from openpilot.frogpilot.common.frogpilot_download_utilities import github_rate_limited
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import clean_model_name, is_url_pingable
|
||||
|
||||
BUCKET = os.environ.get("STATS_BUCKET", "")
|
||||
ORG_ID = os.environ.get("STATS_ORG_ID", "")
|
||||
TOKEN = os.environ.get("STATS_TOKEN", "")
|
||||
STATS_URL = os.environ.get("STATS_URL", "")
|
||||
from openpilot.frogpilot.common.frogpilot_utilities import clean_model_name, get_frogpilot_api_info, is_url_pingable
|
||||
from openpilot.frogpilot.common.frogpilot_variables import FROGPILOT_API
|
||||
|
||||
BASE_URL = "https://nominatim.openstreetmap.org"
|
||||
GITHUB_API_URL = "https://api.github.com/repos/FrogAi/FrogPilot/commits"
|
||||
@@ -27,7 +14,7 @@ MINIMUM_POPULATION = 100_000
|
||||
|
||||
TRACKED_BRANCHES = ["FrogPilot", "FrogPilot-Staging", "FrogPilot-Testing"]
|
||||
|
||||
def get_branch_commits(now):
|
||||
def get_branch_commits():
|
||||
commits = []
|
||||
|
||||
with requests.Session() as session:
|
||||
@@ -48,13 +35,16 @@ def get_branch_commits(now):
|
||||
|
||||
sha = response.json().get("sha")
|
||||
if sha:
|
||||
commits.append(Point("branch_commits").field("commit", sha).tag("branch", branch).time(now))
|
||||
commits.append({"branch": branch, "commit": sha})
|
||||
except requests.exceptions.RequestException as exception:
|
||||
print(f"Failed to get commit for {branch}: {exception}")
|
||||
|
||||
return commits
|
||||
|
||||
def get_city_center(latitude, longitude):
|
||||
if latitude == 0 and longitude == 0:
|
||||
return (0.0, 0.0, "N/A", "N/A", "N/A")
|
||||
|
||||
try:
|
||||
with requests.Session() as session:
|
||||
session.headers.update({
|
||||
@@ -137,10 +127,10 @@ def get_city_center(latitude, longitude):
|
||||
return (0.0, 0.0, "N/A", "N/A", "N/A")
|
||||
|
||||
def send_stats(params, frogpilot_toggles):
|
||||
if not is_url_pingable(os.environ.get("STATS_URL", "")):
|
||||
if not is_url_pingable(f"{FROGPILOT_API}"):
|
||||
return
|
||||
|
||||
build_metadata = get_build_metadata()
|
||||
api_token, build_metadata, device_type, dongle_id = get_frogpilot_api_info()
|
||||
|
||||
car_params = "{}"
|
||||
msg_bytes = params.get("CarParamsPersistent")
|
||||
@@ -160,7 +150,6 @@ def send_stats(params, frogpilot_toggles):
|
||||
fpcp_dict.pop("carVin", None)
|
||||
frogpilot_car_params = json.dumps(fpcp_dict)
|
||||
|
||||
dongle_id = params.get("FrogPilotDongleId")
|
||||
frogpilot_stats = params.get("FrogPilotStats")
|
||||
|
||||
location = json.loads(params.get("LastGPSPosition") or "{}") or {}
|
||||
@@ -168,61 +157,43 @@ def send_stats(params, frogpilot_toggles):
|
||||
original_longitude = location.get("longitude", 0.0)
|
||||
latitude, longitude, city, state, country = get_city_center(original_latitude, original_longitude)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
payload = {
|
||||
"api_token": api_token,
|
||||
"branch_commits": get_branch_commits(),
|
||||
"build_metadata": build_metadata,
|
||||
"model_scores": [],
|
||||
"user_stats": {
|
||||
"calibrated_lateral_acceleration": params.get("CalibratedLateralAcceleration"),
|
||||
"calibration_progress": params.get("CalibrationProgress"),
|
||||
"car_params": car_params,
|
||||
"city": city,
|
||||
"country": country,
|
||||
"device": device_type,
|
||||
"frogpilot_car_params": frogpilot_car_params,
|
||||
"frogpilot_dongle_id": dongle_id,
|
||||
"frogpilot_stats": json.dumps(frogpilot_stats),
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"state": state,
|
||||
"toggles": json.dumps(frogpilot_toggles.__dict__),
|
||||
"using_default_model": params.get("DrivingModel").endswith("_default"),
|
||||
},
|
||||
}
|
||||
|
||||
theme_attributes = sorted(["color_scheme", "distance_icons", "icon_pack", "signal_icons", "sound_pack"])
|
||||
theme_counts = Counter(getattr(frogpilot_toggles, attribute).replace("-animated", "") for attribute in theme_attributes)
|
||||
winners = [theme for theme, count in theme_counts.items() if count == max(theme_counts.values(), default=0)]
|
||||
if len(winners) > 1 and "stock" in winners:
|
||||
winners.remove("stock")
|
||||
selected_theme = random.choice(winners).replace("-user_created", "").replace("_", " ") if winners else "stock"
|
||||
|
||||
user_point = (
|
||||
Point("user_stats")
|
||||
.field("calibrated_lateral_acceleration", params.get("CalibratedLateralAcceleration"))
|
||||
.field("calibration_progress", params.get("CalibrationProgress"))
|
||||
.field("car_params", car_params)
|
||||
.field("city", city)
|
||||
.field("commit", build_metadata.openpilot.git_commit)
|
||||
.field("country", country)
|
||||
.field("device", HARDWARE.get_device_type())
|
||||
.field("event", 1)
|
||||
.field("frogpilot_car_params", frogpilot_car_params)
|
||||
.field("frogpilot_stats", json.dumps(frogpilot_stats))
|
||||
.field("latitude", latitude)
|
||||
.field("longitude", longitude)
|
||||
.field("state", state)
|
||||
.field("stats", json.dumps(frogpilot_stats)) # Remove in the future
|
||||
.field("theme", selected_theme.title())
|
||||
.field("toggles", json.dumps(frogpilot_toggles.__dict__))
|
||||
.field("tuning_level", params.get("TuningLevel") + 1 if params.get_bool("TuningLevelConfirmed") else 0)
|
||||
.field("using_default_model", params.get("DrivingModel").endswith("_default"))
|
||||
.tag("branch", build_metadata.channel)
|
||||
.tag("dongle_id", dongle_id)
|
||||
.time(now)
|
||||
)
|
||||
|
||||
model_points = []
|
||||
for model_name, data in sorted(params.get("ModelDrivesAndScores").items()):
|
||||
drives = data.get("Drives", 0)
|
||||
score = data.get("Score", 0)
|
||||
|
||||
if drives > 0:
|
||||
point = (
|
||||
Point("model_scores")
|
||||
.field("drives", int(drives))
|
||||
.field("score", int(score))
|
||||
.tag("dongle_id", dongle_id)
|
||||
.tag("model_name", clean_model_name(model_name))
|
||||
.time(now)
|
||||
)
|
||||
model_points.append(point)
|
||||
payload["model_scores"].append({
|
||||
"model_name": clean_model_name(model_name),
|
||||
"drives": int(drives),
|
||||
"score": int(score),
|
||||
})
|
||||
|
||||
all_points = get_branch_commits(now) + model_points + [user_point]
|
||||
|
||||
client = InfluxDBClient(org=ORG_ID, timeout=60000, token=TOKEN, url=STATS_URL)
|
||||
try:
|
||||
client.write_api(write_options=SYNCHRONOUS).write(bucket=BUCKET, record=all_points)
|
||||
response = requests.post(f"{FROGPILOT_API}/stats", json=payload, headers={"Content-Type": "application/json", "User-Agent": "frogpilot-api/1.0"}, timeout=30)
|
||||
response.raise_for_status()
|
||||
print("Successfully sent FrogPilot stats!")
|
||||
except Exception as error:
|
||||
except requests.exceptions.RequestException as error:
|
||||
print(f"Failed to send stats: {error}")
|
||||
|
||||
@@ -213,6 +213,9 @@ class MapSpeedLogger:
|
||||
current_latitude = gps_location.latitude
|
||||
current_longitude = gps_location.longitude
|
||||
|
||||
if current_latitude == 0 and current_longitude == 0:
|
||||
return
|
||||
|
||||
if self.previous_coordinates is None:
|
||||
self.previous_coordinates = {"latitude": current_latitude, "longitude": current_longitude}
|
||||
return
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,20 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: flatbuffers
|
||||
Version: 25.9.23
|
||||
Summary: The FlatBuffers serialization format for Python
|
||||
Home-page: https://google.github.io/flatbuffers/
|
||||
Author: Derek Bailey
|
||||
Author-email: derekbailey@google.com
|
||||
License: Apache 2.0
|
||||
Project-URL: Documentation, https://google.github.io/flatbuffers/
|
||||
Project-URL: Source, https://github.com/google/flatbuffers
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 2
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
License-File: ../LICENSE
|
||||
|
||||
Python runtime library for use with the `Flatbuffers <https://google.github.io/flatbuffers/>`_ serialization format.
|
||||
@@ -1,15 +0,0 @@
|
||||
flatbuffers/__init__.py,sha256=vJZrqZOOTKdBNMa_iTKUA6WJG_c_NzKGpFXOe1Igtiw,751
|
||||
flatbuffers/_version.py,sha256=GVL6M_yJfoAklDfbfTYFV72LDbIU-YgRXL4d1yX3EVw,695
|
||||
flatbuffers/builder.py,sha256=uusDhSDKpnLLz6KR4vflC7T74VNwQew9QRkRuxGZTDg,25048
|
||||
flatbuffers/compat.py,sha256=ihBSpWDCSL-vgLSyZtcu8LX3ZI3wz9LhtqItY2GQZgg,2373
|
||||
flatbuffers/encode.py,sha256=2Or3mgWRAkJiWg-GgYasDU4zIHpQU3W06fmIhwbz5uM,1550
|
||||
flatbuffers/flexbuffers.py,sha256=yF8Wr4Lo8WJb-pj9NNaIYxLwzlHHyTroM0iO8fyDwbU,44454
|
||||
flatbuffers/number_types.py,sha256=ijO0QcJiuxlQegoBOed0v9m0DdzTZHWxpTBZUqzsWHA,3762
|
||||
flatbuffers/packer.py,sha256=LNWym8YgFRqHjcPeGpYY3inCGWH6XnbkQKtAPtFEVas,1164
|
||||
flatbuffers/table.py,sha256=ciYTmq_CzAuYpb3KAVnl75M84ieChfbyKne-dFHzwwU,4818
|
||||
flatbuffers/util.py,sha256=mRVQ1VoHp0MJMNtRTUGVzALwN4T_C-U14tMbj99py2A,1608
|
||||
flatbuffers-25.9.23.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
||||
flatbuffers-25.9.23.dist-info/METADATA,sha256=tTKSAMim3fxiII0atPOplikAqxp8vZwSsKE-vUlqFcE,875
|
||||
flatbuffers-25.9.23.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
|
||||
flatbuffers-25.9.23.dist-info/top_level.txt,sha256=UXVWLA8ys6HeqTz6rfKesocUq6ln-ZL8mhZC_cq5BEc,12
|
||||
flatbuffers-25.9.23.dist-info/RECORD,,
|
||||
@@ -1,6 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.45.1)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
flatbuffers
|
||||
+1
-1
@@ -14,4 +14,4 @@
|
||||
|
||||
# Placeholder, to be updated during the release process
|
||||
# by the setup.py
|
||||
__version__ = "25.9.23"
|
||||
__version__ = "25.12.19"
|
||||
|
||||
+62
-74
@@ -159,20 +159,20 @@ class Builder(object):
|
||||
self.vtables = {}
|
||||
self.nested = False
|
||||
self.forceDefaults = False
|
||||
self.sharedStrings = {}
|
||||
self.sharedStrings = None
|
||||
## @endcond
|
||||
self.finished = False
|
||||
|
||||
def Clear(self) -> None:
|
||||
def Clear(self):
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
self.current_vtable = None
|
||||
self.head = UOffsetTFlags.py_type(len(self.Bytes))
|
||||
self.head = len(self.Bytes)
|
||||
self.minalign = 1
|
||||
self.objectEnd = None
|
||||
self.vtables = {}
|
||||
self.nested = False
|
||||
self.forceDefaults = False
|
||||
self.sharedStrings = {}
|
||||
self.sharedStrings = None
|
||||
self.vectorNumElems = None
|
||||
## @endcond
|
||||
self.finished = False
|
||||
@@ -192,7 +192,7 @@ class Builder(object):
|
||||
if not self.finished:
|
||||
raise BuilderNotFinishedError()
|
||||
|
||||
return self.Bytes[self.Head() :]
|
||||
return self.Bytes[self.head :]
|
||||
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
def StartObject(self, numfields):
|
||||
@@ -201,7 +201,7 @@ class Builder(object):
|
||||
self.assertNotNested()
|
||||
|
||||
# use 32-bit offsets so that arithmetic doesn't overflow.
|
||||
self.current_vtable = [0 for _ in range_func(numfields)]
|
||||
self.current_vtable = [0] * numfields
|
||||
self.objectEnd = self.Offset()
|
||||
self.nested = True
|
||||
|
||||
@@ -245,7 +245,10 @@ class Builder(object):
|
||||
|
||||
vtKey.append(elem)
|
||||
|
||||
objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd)
|
||||
vtKey.append(objectSize)
|
||||
vtKey = tuple(vtKey)
|
||||
# calculate the size of the object
|
||||
vt2Offset = self.vtables.get(vtKey)
|
||||
if vt2Offset is None:
|
||||
# Did not find a vtable, so write this one to the buffer.
|
||||
@@ -275,7 +278,6 @@ class Builder(object):
|
||||
# The two metadata fields are written last.
|
||||
|
||||
# First, store the object bytesize:
|
||||
objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd)
|
||||
self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize))
|
||||
|
||||
# Second, store the vtable bytesize:
|
||||
@@ -319,7 +321,7 @@ class Builder(object):
|
||||
self.nested = False
|
||||
return self.WriteVtable()
|
||||
|
||||
def growByteBuffer(self):
|
||||
def GrowByteBuffer(self):
|
||||
"""Doubles the size of the byteslice, and copies the old data towards
|
||||
|
||||
the end of the new buffer (since we build the buffer backwards).
|
||||
@@ -350,12 +352,15 @@ class Builder(object):
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
def Offset(self):
|
||||
"""Offset relative to the end of the buffer."""
|
||||
return UOffsetTFlags.py_type(len(self.Bytes) - self.Head())
|
||||
return len(self.Bytes) - self.head
|
||||
|
||||
def Pad(self, n):
|
||||
"""Pad places zeros at the current offset."""
|
||||
for i in range_func(n):
|
||||
self.Place(0, N.Uint8Flags)
|
||||
if n <= 0:
|
||||
return
|
||||
new_head = self.head - n
|
||||
self.Bytes[new_head : self.head] = b"\x00" * n
|
||||
self.head = new_head
|
||||
|
||||
def Prep(self, size, additionalBytes):
|
||||
"""Prep prepares to write an element of `size` after `additional_bytes`
|
||||
@@ -372,15 +377,19 @@ class Builder(object):
|
||||
|
||||
# Find the amount of alignment needed such that `size` is properly
|
||||
# aligned after `additionalBytes`:
|
||||
alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1
|
||||
head = self.head
|
||||
buf_len = len(self.Bytes)
|
||||
alignSize = (~(buf_len - head + additionalBytes)) + 1
|
||||
alignSize &= size - 1
|
||||
|
||||
# Reallocate the buffer if needed:
|
||||
while self.Head() < alignSize + size + additionalBytes:
|
||||
oldBufSize = len(self.Bytes)
|
||||
self.growByteBuffer()
|
||||
updated_head = self.head + len(self.Bytes) - oldBufSize
|
||||
self.head = UOffsetTFlags.py_type(updated_head)
|
||||
needed = alignSize + size + additionalBytes
|
||||
while head < needed:
|
||||
oldBufSize = buf_len
|
||||
self.GrowByteBuffer()
|
||||
buf_len = len(self.Bytes)
|
||||
head += buf_len - oldBufSize
|
||||
self.head = head
|
||||
self.Pad(alignSize)
|
||||
|
||||
def PrependSOffsetTRelative(self, off):
|
||||
@@ -455,7 +464,9 @@ class Builder(object):
|
||||
before calling CreateString.
|
||||
"""
|
||||
|
||||
if s in self.sharedStrings:
|
||||
if not self.sharedStrings:
|
||||
self.sharedStrings = {}
|
||||
elif s in self.sharedStrings:
|
||||
return self.sharedStrings[s]
|
||||
|
||||
off = self.CreateString(s, encoding, errors)
|
||||
@@ -478,16 +489,15 @@ class Builder(object):
|
||||
else:
|
||||
raise TypeError("non-string passed to CreateString")
|
||||
|
||||
self.Prep(N.UOffsetTFlags.bytewidth, (len(x) + 1) * N.Uint8Flags.bytewidth)
|
||||
payload_len = len(x)
|
||||
self.Prep(N.UOffsetTFlags.bytewidth, (payload_len + 1) * N.Uint8Flags.bytewidth)
|
||||
self.Place(0, N.Uint8Flags)
|
||||
|
||||
l = UOffsetTFlags.py_type(len(s))
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
self.head = UOffsetTFlags.py_type(self.Head() - l)
|
||||
## @endcond
|
||||
self.Bytes[self.Head() : self.Head() + l] = x
|
||||
new_head = self.head - payload_len
|
||||
self.head = new_head
|
||||
self.Bytes[new_head : new_head + payload_len] = x
|
||||
|
||||
self.vectorNumElems = len(x)
|
||||
self.vectorNumElems = payload_len
|
||||
return self.EndVector()
|
||||
|
||||
def CreateByteVector(self, x):
|
||||
@@ -501,15 +511,13 @@ class Builder(object):
|
||||
if not isinstance(x, compat.binary_types):
|
||||
raise TypeError("non-byte vector passed to CreateByteVector")
|
||||
|
||||
self.Prep(N.UOffsetTFlags.bytewidth, len(x) * N.Uint8Flags.bytewidth)
|
||||
data_len = len(x)
|
||||
self.Prep(N.UOffsetTFlags.bytewidth, data_len * N.Uint8Flags.bytewidth)
|
||||
new_head = self.head - data_len
|
||||
self.head = new_head
|
||||
self.Bytes[new_head : new_head + data_len] = x
|
||||
|
||||
l = UOffsetTFlags.py_type(len(x))
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
self.head = UOffsetTFlags.py_type(self.Head() - l)
|
||||
## @endcond
|
||||
self.Bytes[self.Head() : self.Head() + l] = x
|
||||
|
||||
self.vectorNumElems = len(x)
|
||||
self.vectorNumElems = data_len
|
||||
return self.EndVector()
|
||||
|
||||
def CreateNumpyVector(self, x):
|
||||
@@ -536,14 +544,14 @@ class Builder(object):
|
||||
else:
|
||||
x_lend = x.byteswap(inplace=False)
|
||||
|
||||
# Calculate total length
|
||||
l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size)
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
self.head = UOffsetTFlags.py_type(self.Head() - l)
|
||||
## @endcond
|
||||
|
||||
# tobytes ensures c_contiguous ordering
|
||||
self.Bytes[self.Head() : self.Head() + l] = x_lend.tobytes(order="C")
|
||||
payload = x_lend.tobytes(order="C")
|
||||
|
||||
# Calculate total length
|
||||
payload_len = len(payload)
|
||||
new_head = self.head - payload_len
|
||||
self.head = new_head
|
||||
self.Bytes[new_head : new_head + payload_len] = payload
|
||||
|
||||
self.vectorNumElems = x.size
|
||||
return self.EndVector()
|
||||
@@ -613,11 +621,11 @@ class Builder(object):
|
||||
|
||||
self.PrependUOffsetTRelative(rootTable)
|
||||
if sizePrefix:
|
||||
size = len(self.Bytes) - self.Head()
|
||||
size = len(self.Bytes) - self.head
|
||||
N.enforce_number(size, N.Int32Flags)
|
||||
self.PrependInt32(size)
|
||||
self.finished = True
|
||||
return self.Head()
|
||||
return self.head
|
||||
|
||||
def Finish(self, rootTable, file_identifier=None):
|
||||
"""Finish finalizes a buffer, pointing to the given `rootTable`."""
|
||||
@@ -811,8 +819,9 @@ class Builder(object):
|
||||
"""
|
||||
|
||||
N.enforce_number(x, flags)
|
||||
self.head = self.head - flags.bytewidth
|
||||
encode.Write(flags.packer_type, self.Bytes, self.Head(), x)
|
||||
new_head = self.head - flags.bytewidth
|
||||
self.head = new_head
|
||||
encode.Write(flags.packer_type, self.Bytes, new_head, x)
|
||||
|
||||
def PlaceVOffsetT(self, x):
|
||||
"""PlaceVOffsetT prepends a VOffsetT to the Builder, without checking
|
||||
@@ -820,8 +829,9 @@ class Builder(object):
|
||||
for space.
|
||||
"""
|
||||
N.enforce_number(x, N.VOffsetTFlags)
|
||||
self.head = self.head - N.VOffsetTFlags.bytewidth
|
||||
encode.Write(packer.voffset, self.Bytes, self.Head(), x)
|
||||
new_head = self.head - N.VOffsetTFlags.bytewidth
|
||||
self.head = new_head
|
||||
encode.Write(packer.voffset, self.Bytes, new_head, x)
|
||||
|
||||
def PlaceSOffsetT(self, x):
|
||||
"""PlaceSOffsetT prepends a SOffsetT to the Builder, without checking
|
||||
@@ -829,8 +839,9 @@ class Builder(object):
|
||||
for space.
|
||||
"""
|
||||
N.enforce_number(x, N.SOffsetTFlags)
|
||||
self.head = self.head - N.SOffsetTFlags.bytewidth
|
||||
encode.Write(packer.soffset, self.Bytes, self.Head(), x)
|
||||
new_head = self.head - N.SOffsetTFlags.bytewidth
|
||||
self.head = new_head
|
||||
encode.Write(packer.soffset, self.Bytes, new_head, x)
|
||||
|
||||
def PlaceUOffsetT(self, x):
|
||||
"""PlaceUOffsetT prepends a UOffsetT to the Builder, without checking
|
||||
@@ -838,33 +849,10 @@ class Builder(object):
|
||||
for space.
|
||||
"""
|
||||
N.enforce_number(x, N.UOffsetTFlags)
|
||||
self.head = self.head - N.UOffsetTFlags.bytewidth
|
||||
encode.Write(packer.uoffset, self.Bytes, self.Head(), x)
|
||||
new_head = self.head - N.UOffsetTFlags.bytewidth
|
||||
self.head = new_head
|
||||
encode.Write(packer.uoffset, self.Bytes, new_head, x)
|
||||
|
||||
## @endcond
|
||||
|
||||
|
||||
## @cond FLATBUFFERS_INTERNAL
|
||||
def vtableEqual(a, objectStart, b):
|
||||
"""vtableEqual compares an unwritten vtable to a written vtable."""
|
||||
|
||||
N.enforce_number(objectStart, N.UOffsetTFlags)
|
||||
|
||||
if len(a) * N.VOffsetTFlags.bytewidth != len(b):
|
||||
return False
|
||||
|
||||
for i, elem in enumerate(a):
|
||||
x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth)
|
||||
|
||||
# Skip vtable entries that indicate a default value.
|
||||
if x == 0 and elem == 0:
|
||||
pass
|
||||
else:
|
||||
y = objectStart - elem
|
||||
if x != y:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
## @endcond
|
||||
## @}
|
||||
|
||||
-1592
File diff suppressed because it is too large
Load Diff
-347
@@ -1,347 +0,0 @@
|
||||
Metadata-Version: 2.2
|
||||
Name: h3
|
||||
Version: 4.3.1
|
||||
Summary: Uber's hierarchical hexagonal geospatial indexing system
|
||||
Author-Email: Uber Technologies <ajfriend@gmail.com>
|
||||
Maintainer-Email: AJ Friend <ajfriend@gmail.com>
|
||||
License:
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: Intended Audience :: Science/Research
|
||||
Classifier: License :: OSI Approved :: Apache Software License
|
||||
Classifier: Programming Language :: C
|
||||
Classifier: Programming Language :: Cython
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3.9
|
||||
Classifier: Programming Language :: Python :: 3.10
|
||||
Classifier: Programming Language :: Python :: 3.11
|
||||
Classifier: Programming Language :: Python :: 3.12
|
||||
Classifier: Programming Language :: Python :: 3.13
|
||||
Classifier: Operating System :: MacOS :: MacOS X
|
||||
Classifier: Operating System :: POSIX :: Linux
|
||||
Classifier: Operating System :: Microsoft :: Windows
|
||||
Classifier: Topic :: Scientific/Engineering :: GIS
|
||||
Project-URL: Homepage, https://github.com/uber/h3-py
|
||||
Project-URL: Documentation, https://uber.github.io/h3-py/
|
||||
Project-URL: Bug Tracker, https://github.com/uber/h3-py/issues
|
||||
Project-URL: Discussions, https://github.com/uber/h3-py/discussions
|
||||
Project-URL: Changelog, https://uber.github.io/h3-py/_changelog.html
|
||||
Requires-Python: >=3.8
|
||||
Provides-Extra: numpy
|
||||
Requires-Dist: numpy; extra == "numpy"
|
||||
Provides-Extra: test
|
||||
Requires-Dist: pytest; extra == "test"
|
||||
Requires-Dist: pytest-cov; extra == "test"
|
||||
Requires-Dist: ruff; extra == "test"
|
||||
Requires-Dist: numpy; extra == "test"
|
||||
Provides-Extra: all
|
||||
Requires-Dist: h3[test]; extra == "all"
|
||||
Requires-Dist: jupyter-book; extra == "all"
|
||||
Requires-Dist: sphinx>=7.3.3; extra == "all"
|
||||
Requires-Dist: jupyterlab; extra == "all"
|
||||
Requires-Dist: jupyterlab-geojson; extra == "all"
|
||||
Requires-Dist: geopandas; extra == "all"
|
||||
Requires-Dist: geodatasets; extra == "all"
|
||||
Requires-Dist: matplotlib; extra == "all"
|
||||
Requires-Dist: contextily; extra == "all"
|
||||
Requires-Dist: cartopy; extra == "all"
|
||||
Requires-Dist: geoviews; extra == "all"
|
||||
Description-Content-Type: text/markdown
|
||||
|
||||
<img align="right" src="https://uber.github.io/img/h3Logo-color.svg" alt="H3 Logo" width="130">
|
||||
|
||||
# **h3-py**: Uber's H3 Hexagonal Hierarchical Geospatial Indexing System in Python
|
||||
|
||||
[](https://badge.fury.io/py/h3)
|
||||
[](https://pypistats.org/packages/h3)
|
||||
[](https://anaconda.org/conda-forge/h3-py)
|
||||
[](https://github.com/uber/h3/releases/tag/v4.3.0)
|
||||
[](https://github.com/uber/h3-py/blob/master/LICENSE)
|
||||
|
||||
[](https://github.com/uber/h3-py/actions)
|
||||
[](https://github.com/uber/h3-py/blob/master/.github/workflows/lint_and_coverage.yml#L31) <!-- 100% coverage is enforced in CI -->
|
||||
|
||||
|
||||
Python bindings for the [H3 core library](https://h3geo.org/).
|
||||
|
||||
- Documentation: [uber.github.io/h3-py](https://uber.github.io/h3-py)
|
||||
- GitHub repo: [github.com/uber/h3-py](https://github.com/uber/h3-py)
|
||||
|
||||
## Installation
|
||||
|
||||
From [PyPI](https://pypi.org/project/h3/):
|
||||
|
||||
```console
|
||||
pip install h3
|
||||
```
|
||||
|
||||
From [conda](https://github.com/conda-forge/h3-py-feedstock):
|
||||
|
||||
```console
|
||||
conda config --add channels conda-forge
|
||||
conda install h3-py
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
>>> import h3
|
||||
>>> lat, lng = 37.769377, -122.388903
|
||||
>>> resolution = 9
|
||||
>>> h3.latlng_to_cell(lat, lng, resolution)
|
||||
'89283082e73ffff'
|
||||
```
|
||||
|
||||
|
||||
## APIs
|
||||
|
||||
[api_comparison]: https://uber.github.io/h3-py/api_comparison
|
||||
[api_quick]: https://uber.github.io/h3-py/api_quick
|
||||
|
||||
We provide [multiple APIs][api_comparison] in `h3-py`.
|
||||
|
||||
- All APIs have the same set of functions;
|
||||
see the [API reference][api_quick].
|
||||
- The APIs differ only in their input/output formats;
|
||||
see the [API comparison page][api_comparison].
|
||||
|
||||
|
||||
## Example gallery
|
||||
|
||||
Browse [a collection of example notebooks](https://github.com/uber/h3-py-notebooks),
|
||||
and if you have examples or visualizations of your own, please feel free
|
||||
to contribute!
|
||||
|
||||
[walkthrough]: https://nbviewer.jupyter.org/github/uber/h3-py-notebooks/blob/master/notebooks/usage.ipynb
|
||||
|
||||
We also have an introductory [walkthrough of the API][walkthrough].
|
||||
|
||||
|
||||
## Versioning
|
||||
|
||||
<!-- todo: this should just be the h3.versions() docstring, yeah? -->
|
||||
|
||||
`h3-py` wraps the [H3 core library](https://github.com/uber/h3),
|
||||
which is written in C.
|
||||
The C and Python projects each employ
|
||||
[semantic versioning](https://semver.org/),
|
||||
where versions take the form `X.Y.Z`.
|
||||
|
||||
The `h3-py` version string is guaranteed to match the C library string
|
||||
in both *major* and *minor* numbers (`X.Y`), but may differ on the
|
||||
*patch* (`Z`) number.
|
||||
This convention provides users with information on breaking changes and
|
||||
feature additions, while providing downstream bindings (like this one!)
|
||||
with the versioning freedom to fix bugs.
|
||||
|
||||
Use `h3.versions()` to see the version numbers for both
|
||||
`h3-py` and the C library. For example,
|
||||
|
||||
```python
|
||||
>>> import h3
|
||||
>>> h3.versions()
|
||||
{'c': '4.1.0', 'python': '4.1.1'}
|
||||
```
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
h3/CMakeLists.txt,sha256=8zQ0011t8bmsJw_GiKOFbkTZnquBh6ESCMZyLrm9u_U,22
|
||||
h3/__init__.py,sha256=-i_HfsVLg1wW2wX8p74WIxHXi7NPO1GSVtjILstnS8U,575
|
||||
h3/_h3shape.py,sha256=hhsBJyuy5MqDhG_BixzbCTOfQgaO6Gf2olFEYd6iLKE,8559
|
||||
h3/_version.py,sha256=Tjq_BBPmWroGgfnrf7u9TpAWss9iA5PnWSv8kkZkJY8,88
|
||||
h3/_cy/CMakeLists.txt,sha256=-h6Dk_V8VYydmDht9zCOv7wbSOl_G4MtlajIrj2D8hE,1528
|
||||
h3/_cy/__init__.py,sha256=-f0Zqm46bnFuV0-Vs2FJMhODzbL1Oow-zuRJQS5dBFs,2488
|
||||
h3/_cy/cells.cpython-312-aarch64-linux-gnu.so,sha256=IT938GoA2i_KXkLbzfy84nZB85Jl9g4KSm7rdHWBHPY,469320
|
||||
h3/_cy/cells.pxd,sha256=9GydN6Nc4vJFU0nVyCP8BvDA4P702OX2LDQdkAnHv9I,1326
|
||||
h3/_cy/cells.pyx,sha256=TcEjufzELbfLGxQ10IA1aFX-nQt8sDx6JT_HhKQtsNc,9517
|
||||
h3/_cy/edges.cpython-312-aarch64-linux-gnu.so,sha256=8nCk6r3A9PISVMLdzHTiatYt3dqNDeFkQ-sfemWmvYM,402824
|
||||
h3/_cy/edges.pxd,sha256=_zUOkV8aeWhk_xjicSWBDHNOp3vMEeg8o2Da06iYn38,561
|
||||
h3/_cy/edges.pyx,sha256=1ECoK-OP3_BplwLZqwqNwItMLNIja6kVMzMeo7maHz8,2487
|
||||
h3/_cy/error_system.cpython-312-aarch64-linux-gnu.so,sha256=R4fc8to7fKrP7QBXX5KB7SXQzasH_vo81f7U6bGs1mk,202360
|
||||
h3/_cy/error_system.pxd,sha256=Dd2LjHcmlkCcs5Tr_HSp7xcn8YsQq2Evk4HxtIZF9m4,109
|
||||
h3/_cy/error_system.pyx,sha256=gyg7NPuY2nfsj2QnJC11OTZUCXLvNybpcgNC25Jor7E,6908
|
||||
h3/_cy/h3api.h,sha256=lQHNByNGZG6AkVjpIB2OZEv7fnAbBfCGqe4oqx0Hgd0,26418
|
||||
h3/_cy/h3lib.pxd,sha256=d37c0MiHuWQL28ogaxiLjKq1P4lh_gdIPRZrjizHi2g,6955
|
||||
h3/_cy/latlng.cpython-312-aarch64-linux-gnu.so,sha256=Nr-18UTddm3XyqtmVqrxgbWPbihilri0Od-gE2FRr1Q,471768
|
||||
h3/_cy/latlng.pxd,sha256=WjOjY4jH_2lK6hnaVXCusqLg2DvvZ4rvAJyXeCQd2uk,266
|
||||
h3/_cy/latlng.pyx,sha256=FW8L4LEr6l7qhsRL28mFakVLHnfqSCKQDQ6Ued-nLks,8370
|
||||
h3/_cy/memory.cpython-312-aarch64-linux-gnu.so,sha256=FXPAzIgzILQh4LxXlOchgkLY6_kpFWmzb3zsIFFL9lg,336208
|
||||
h3/_cy/memory.pxd,sha256=jXGN68hdcPUyA_yFCesZxCksHwmHMO4Ah-I9ZwYBvXs,236
|
||||
h3/_cy/memory.pyx,sha256=KZo65H3rXn290iH05ZNkyt78mKEGIaVjzmgYdOon_PU,7103
|
||||
h3/_cy/to_multipoly.cpython-312-aarch64-linux-gnu.so,sha256=4r6p7uq0B_Ln85xK_P9HvC59LtTR-ZAXGYXMG8CUwBk,402440
|
||||
h3/_cy/to_multipoly.pyx,sha256=bpaP3rGl5mtWTp1aH0ksL-gMi_tobSxjnZvxX6B2R4o,1369
|
||||
h3/_cy/util.cpython-312-aarch64-linux-gnu.so,sha256=zIQQ7i19XEZiT4I6hMg3RdnXxb0ZDjRCU1ohQ9pwoxY,267856
|
||||
h3/_cy/util.pxd,sha256=9JeCVVOOxfd3QoK4q_W-yIhmEASq_X2Y-A_Crn2bvZg,346
|
||||
h3/_cy/util.pyx,sha256=9woUsIwPG18UmjDbw8QFNnIXT62HMOUKGUmGIKt_ONo,2348
|
||||
h3/_cy/vertex.cpython-312-aarch64-linux-gnu.so,sha256=5-tDMKX5uWCv-2W3gVPhAwFbcB8f5q092KLuUa5oChA,337024
|
||||
h3/_cy/vertex.pxd,sha256=lnRECjEFpohFr7iB6d2UrTN-s37UzXnSpaP_UV5866I,229
|
||||
h3/_cy/vertex.pyx,sha256=_26ur2q3CZzb0BGQ85Lvbjq9dRWscvjAsKlgmCAqVHc,910
|
||||
h3/api/__init__.py,sha256=X6P438-1N_2jWlFKNZ8q_jUop7ICRhwwa4I0RuxtQIQ,114
|
||||
h3/api/basic_int/__init__.py,sha256=pg0QGCIGIyT0WTnD2vDqP-LyW0m5E1qd9F19l6wf3Rs,25924
|
||||
h3/api/basic_int/_convert.py,sha256=GmVFFTT-mMHJ_Newx-6aF8TAtKBTrXglMx5RNV9w0Nk,187
|
||||
h3/api/basic_str/__init__.py,sha256=pg0QGCIGIyT0WTnD2vDqP-LyW0m5E1qd9F19l6wf3Rs,25924
|
||||
h3/api/basic_str/_convert.py,sha256=9B7uHdbUJhDb0GUEdSg-dUQpNCmvPviK6_DX99YS5UE,256
|
||||
h3/api/memview_int/__init__.py,sha256=pg0QGCIGIyT0WTnD2vDqP-LyW0m5E1qd9F19l6wf3Rs,25924
|
||||
h3/api/memview_int/_convert.py,sha256=ef5Omw0GJsUlR8r66O1p8f9IEEhCeTdDjlvN2A0urjA,105
|
||||
h3/api/numpy_int/__init__.py,sha256=pg0QGCIGIyT0WTnD2vDqP-LyW0m5E1qd9F19l6wf3Rs,25924
|
||||
h3/api/numpy_int/_convert.py,sha256=dhZLocPjucl6Uu872k1tAcuWXBRT29oUG8qFMoleulE,288
|
||||
include/h3/h3api.h,sha256=lQHNByNGZG6AkVjpIB2OZEv7fnAbBfCGqe4oqx0Hgd0,26418
|
||||
lib64/libh3.a,sha256=daDl9sRycc5tHGPbiHyd-IYfmyaXFQKnB-xES26wv4Q,170004
|
||||
lib64/cmake/h3/h3Config.cmake,sha256=QwISc49jy-ZH9Ba1YCQnick54sapi_pZDEJjDJyMrb4,959
|
||||
lib64/cmake/h3/h3ConfigVersion.cmake,sha256=Nf3JY8h9E9o1YHZ9TuUc_XN5CyS3yxa4pp5PTIEViKM,2762
|
||||
lib64/cmake/h3/h3Targets-release.cmake,sha256=pnJffyu5LDg0GeSMTTwBoVLamlDKo8XrjrCEyN7aP0w,797
|
||||
lib64/cmake/h3/h3Targets.cmake,sha256=K18pICoM546T1tLsCSvdLlnLf1-GyFYPqMq8G0mZobM,4217
|
||||
h3-4.3.1.dist-info/METADATA,sha256=XJDmH460i2LSmjPAibQR_6XHyedrvDe_0tXZM3v-7n0,18459
|
||||
h3-4.3.1.dist-info/WHEEL,sha256=uOikQdbS4EYP0OE7pa4p-weSIqjfixseckdKxHaHlvE,198
|
||||
h3-4.3.1.dist-info/RECORD,,
|
||||
h3-4.3.1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
||||
@@ -1,7 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: scikit-build-core 0.11.5
|
||||
Root-Is-Purelib: false
|
||||
Tag: cp312-cp312-manylinux_2_17_aarch64
|
||||
Tag: cp312-cp312-manylinux2014_aarch64
|
||||
Tag: cp312-cp312-manylinux_2_28_aarch64
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-1
@@ -1 +0,0 @@
|
||||
add_subdirectory(_cy)
|
||||
Vendored
+4
@@ -26,4 +26,8 @@ from ._cy import (
|
||||
H3MemoryAllocError,
|
||||
H3MemoryBoundsError,
|
||||
H3OptionInvalidError,
|
||||
H3IndexInvalidError,
|
||||
H3BaseCellDomainError,
|
||||
H3DigitDomainError,
|
||||
H3DeletedDigitError,
|
||||
)
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
macro(add_cython_file filename)
|
||||
add_custom_command(
|
||||
OUTPUT "${filename}.c"
|
||||
COMMENT
|
||||
"Making ${CMAKE_CURRENT_BINARY_DIR}/${filename}.c from ${CMAKE_CURRENT_SOURCE_DIR}/${filename}.pyx"
|
||||
COMMAND Python::Interpreter -m cython
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${filename}.pyx" --output-file "${filename}.c" -I ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS "${filename}.pyx"
|
||||
VERBATIM)
|
||||
|
||||
python_add_library(${filename} MODULE "${filename}.c" WITH_SOABI)
|
||||
|
||||
set_property(TARGET ${filename} PROPERTY C_STANDARD 99)
|
||||
target_link_libraries(${filename} PRIVATE h3)
|
||||
install(TARGETS ${filename} LIBRARY DESTINATION ${SKBUILD_PROJECT_NAME}/_cy)
|
||||
endmacro()
|
||||
|
||||
# GLOB pattern is recommended against
|
||||
# https://cmake.org/cmake/help/v3.14/command/file.html?highlight=file#filesystem
|
||||
add_cython_file(cells)
|
||||
add_cython_file(edges)
|
||||
add_cython_file(error_system)
|
||||
add_cython_file(latlng)
|
||||
add_cython_file(memory)
|
||||
add_cython_file(vertex)
|
||||
|
||||
add_cython_file(to_multipoly)
|
||||
add_cython_file(util)
|
||||
|
||||
# Include pyx and pxd files in distribution for use by Cython API
|
||||
install(
|
||||
FILES
|
||||
cells.pxd
|
||||
cells.pyx
|
||||
edges.pxd
|
||||
edges.pyx
|
||||
error_system.pyx
|
||||
h3lib.pxd
|
||||
latlng.pxd
|
||||
latlng.pyx
|
||||
memory.pxd
|
||||
memory.pyx
|
||||
util.pxd
|
||||
util.pyx
|
||||
vertex.pxd
|
||||
vertex.pyx
|
||||
DESTINATION
|
||||
${SKBUILD_PROJECT_NAME}/_cy
|
||||
)
|
||||
+10
@@ -14,10 +14,13 @@ function interface and input conversion (string to int, for instance).
|
||||
"""
|
||||
|
||||
from .cells import (
|
||||
is_valid_index,
|
||||
is_valid_cell,
|
||||
is_pentagon,
|
||||
get_base_cell_number,
|
||||
get_resolution,
|
||||
get_index_digit,
|
||||
construct_cell,
|
||||
cell_to_parent,
|
||||
grid_distance,
|
||||
grid_disk,
|
||||
@@ -109,4 +112,11 @@ from .error_system import (
|
||||
H3MemoryAllocError,
|
||||
H3MemoryBoundsError,
|
||||
H3OptionInvalidError,
|
||||
H3IndexInvalidError,
|
||||
H3BaseCellDomainError,
|
||||
H3DigitDomainError,
|
||||
H3DeletedDigitError,
|
||||
|
||||
get_H3_ERROR_END,
|
||||
error_code_to_exception,
|
||||
)
|
||||
|
||||
Binary file not shown.
-27
@@ -1,27 +0,0 @@
|
||||
from .h3lib cimport bool, int64_t, H3int
|
||||
|
||||
cpdef bool is_valid_cell(H3int h)
|
||||
cpdef bool is_pentagon(H3int h)
|
||||
cpdef int get_base_cell_number(H3int h) except -1
|
||||
cpdef int get_resolution(H3int h) except -1
|
||||
cpdef int grid_distance(H3int h1, H3int h2) except -1
|
||||
cpdef H3int[:] grid_disk(H3int h, int k)
|
||||
cpdef H3int[:] grid_ring(H3int h, int k)
|
||||
cpdef H3int cell_to_parent(H3int h, res=*) except 0
|
||||
cpdef int64_t cell_to_children_size(H3int h, res=*) except -1
|
||||
cpdef H3int[:] cell_to_children(H3int h, res=*)
|
||||
cpdef H3int cell_to_center_child(H3int h, res=*) except 0
|
||||
cpdef int64_t cell_to_child_pos(H3int child, int parent_res) except -1
|
||||
cpdef H3int child_pos_to_cell(H3int parent, int child_res, int64_t child_pos) except 0
|
||||
cpdef H3int[:] compact_cells(const H3int[:] hu)
|
||||
cpdef H3int[:] uncompact_cells(const H3int[:] hc, int res)
|
||||
cpdef int64_t get_num_cells(int resolution) except -1
|
||||
cpdef double average_hexagon_area(int resolution, unit=*) except -1
|
||||
cpdef double cell_area(H3int h, unit=*) except -1
|
||||
cpdef H3int[:] grid_path_cells(H3int start, H3int end)
|
||||
cpdef bool is_res_class_iii(H3int h)
|
||||
cpdef H3int[:] get_pentagons(int res)
|
||||
cpdef H3int[:] get_res0_cells()
|
||||
cpdef get_icosahedron_faces(H3int h)
|
||||
cpdef (int, int) cell_to_local_ij(H3int origin, H3int h) except *
|
||||
cpdef H3int local_ij_to_cell(H3int origin, int i, int j) except 0
|
||||
-419
@@ -1,419 +0,0 @@
|
||||
cimport h3lib
|
||||
from .h3lib cimport bool, int64_t, H3int, H3ErrorCodes
|
||||
|
||||
from .util cimport (
|
||||
check_cell,
|
||||
check_res, # we don't use?
|
||||
check_distance,
|
||||
)
|
||||
|
||||
from .error_system cimport (
|
||||
check_for_error,
|
||||
check_for_error_msg,
|
||||
)
|
||||
|
||||
from .memory cimport (
|
||||
H3MemoryManager,
|
||||
int_mv,
|
||||
)
|
||||
|
||||
# todo: add notes about Cython exception handling
|
||||
|
||||
|
||||
# bool is a python type, so we don't need the except clause
|
||||
cpdef bool is_valid_cell(H3int h):
|
||||
"""Validates an H3 cell (hexagon or pentagon)
|
||||
|
||||
Returns
|
||||
-------
|
||||
boolean
|
||||
"""
|
||||
return h3lib.isValidCell(h) == 1
|
||||
|
||||
|
||||
cpdef bool is_pentagon(H3int h):
|
||||
return h3lib.isPentagon(h) == 1
|
||||
|
||||
|
||||
cpdef int get_base_cell_number(H3int h) except -1:
|
||||
check_cell(h)
|
||||
|
||||
return h3lib.getBaseCellNumber(h)
|
||||
|
||||
|
||||
cpdef int get_resolution(H3int h) except -1:
|
||||
"""Returns the resolution of an H3 Index
|
||||
0--15
|
||||
"""
|
||||
check_cell(h)
|
||||
|
||||
return h3lib.getResolution(h)
|
||||
|
||||
|
||||
cpdef int grid_distance(H3int h1, H3int h2) except -1:
|
||||
""" Compute the grid distance between two cells
|
||||
"""
|
||||
cdef:
|
||||
int64_t distance
|
||||
|
||||
check_cell(h1)
|
||||
check_cell(h2)
|
||||
|
||||
check_for_error(
|
||||
h3lib.gridDistance(h1, h2, &distance)
|
||||
)
|
||||
|
||||
return distance
|
||||
|
||||
cpdef H3int[:] grid_disk(H3int h, int k):
|
||||
""" Return cells at grid distance `<= k` from `h`.
|
||||
"""
|
||||
cdef:
|
||||
int64_t n
|
||||
|
||||
check_cell(h)
|
||||
check_distance(k)
|
||||
|
||||
check_for_error(
|
||||
h3lib.maxGridDiskSize(k, &n)
|
||||
)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.gridDisk(h, k, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
cpdef H3int[:] grid_ring(H3int h, int k):
|
||||
""" Return cells at grid distance `== k` from `h`.
|
||||
Collection is "hollow" for k >= 1.
|
||||
"""
|
||||
check_cell(h)
|
||||
check_distance(k)
|
||||
|
||||
n = 6*k if k > 0 else 1
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.gridRing(h, k, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
cpdef H3int cell_to_parent(H3int h, res=None) except 0:
|
||||
cdef:
|
||||
H3int parent
|
||||
|
||||
check_cell(h)
|
||||
if res is None:
|
||||
res = get_resolution(h) - 1
|
||||
|
||||
err = h3lib.cellToParent(h, res, &parent)
|
||||
if err:
|
||||
msg = 'Invalid parent resolution {} for cell {}.'
|
||||
msg = msg.format(res, hex(h))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return parent
|
||||
|
||||
|
||||
cpdef int64_t cell_to_children_size(H3int h, res=None) except -1:
|
||||
cdef:
|
||||
int64_t n
|
||||
|
||||
check_cell(h)
|
||||
if res is None:
|
||||
res = get_resolution(h) + 1
|
||||
|
||||
err = h3lib.cellToChildrenSize(h, res, &n)
|
||||
if err:
|
||||
msg = 'Invalid child resolution {} for cell {}.'
|
||||
msg = msg.format(res, hex(h))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return n
|
||||
|
||||
|
||||
cpdef H3int[:] cell_to_children(H3int h, res=None):
|
||||
check_cell(h)
|
||||
if res is None:
|
||||
res = get_resolution(h) + 1
|
||||
|
||||
n = cell_to_children_size(h, res)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.cellToChildren(h, res, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
|
||||
cpdef H3int cell_to_center_child(H3int h, res=None) except 0:
|
||||
cdef:
|
||||
H3int child
|
||||
|
||||
check_cell(h)
|
||||
if res is None:
|
||||
res = get_resolution(h) + 1
|
||||
|
||||
err = h3lib.cellToCenterChild(h, res, &child)
|
||||
if err:
|
||||
msg = 'Invalid child resolution {} for cell {}.'
|
||||
msg = msg.format(res, hex(h))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return child
|
||||
|
||||
|
||||
cpdef int64_t cell_to_child_pos(H3int child, int parent_res) except -1:
|
||||
cdef:
|
||||
int64_t child_pos
|
||||
|
||||
check_cell(child)
|
||||
err = h3lib.cellToChildPos(child, parent_res, &child_pos)
|
||||
if err:
|
||||
msg = "Couldn't find child pos of cell {} at res {}."
|
||||
msg = msg.format(hex(child), parent_res)
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return child_pos
|
||||
|
||||
|
||||
cpdef H3int child_pos_to_cell(H3int parent, int child_res, int64_t child_pos) except 0:
|
||||
cdef:
|
||||
H3int child
|
||||
|
||||
check_cell(parent)
|
||||
err = h3lib.childPosToCell(child_pos, parent, child_res, &child)
|
||||
if err:
|
||||
msg = "Couldn't find child with pos {} at res {} from parent {}."
|
||||
msg = msg.format(child_pos, child_res, hex(parent))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return child
|
||||
|
||||
|
||||
cpdef H3int[:] compact_cells(const H3int[:] hu):
|
||||
# todo: fix this with my own Cython object "wrapper" class?
|
||||
# everything has a .ptr interface?
|
||||
# todo: the Clib can handle 0-len arrays because it **avoids**
|
||||
# dereferencing the pointer, but Cython's syntax of
|
||||
# `&hu[0]` **requires** a dereference. For Cython, checking for array
|
||||
# length of zero and returning early seems like the easiest solution.
|
||||
# note: open to better ideas!
|
||||
|
||||
if len(hu) == 0:
|
||||
return H3MemoryManager(0).to_mv()
|
||||
|
||||
for h in hu: ## todo: should we have an array version? would that be faster?
|
||||
check_cell(h)
|
||||
|
||||
cdef size_t n = len(hu)
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.compactCells(&hu[0], hmm.ptr, n)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
# todo: https://stackoverflow.com/questions/50684977/cython-exception-type-for-a-function-returning-a-typed-memoryview
|
||||
# apparently, memoryviews are python objects, so we don't need to do the except clause
|
||||
cpdef H3int[:] uncompact_cells(const H3int[:] hc, int res):
|
||||
# todo: the Clib can handle 0-len arrays because it **avoids**
|
||||
# dereferencing the pointer, but Cython's syntax of
|
||||
# `&hc[0]` **requires** a dereference. For Cython, checking for array
|
||||
# length of zero and returning early seems like the easiest solution.
|
||||
# note: open to better ideas!
|
||||
cdef:
|
||||
int64_t n
|
||||
|
||||
|
||||
if len(hc) == 0:
|
||||
return H3MemoryManager(0).to_mv()
|
||||
|
||||
for h in hc:
|
||||
check_cell(h)
|
||||
|
||||
check_for_error(
|
||||
h3lib.uncompactCellsSize(&hc[0], len(hc), res, &n)
|
||||
)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.uncompactCells(
|
||||
&hc[0], # todo: symmetry here with the wrapper object might be nice. hc.ptr / hc.n
|
||||
len(hc),
|
||||
hmm.ptr,
|
||||
hmm.n,
|
||||
res
|
||||
)
|
||||
)
|
||||
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
cpdef int64_t get_num_cells(int resolution) except -1:
|
||||
cdef:
|
||||
int64_t num_cells
|
||||
|
||||
check_for_error(
|
||||
h3lib.getNumCells(resolution, &num_cells)
|
||||
)
|
||||
|
||||
return num_cells
|
||||
|
||||
|
||||
cpdef double average_hexagon_area(int resolution, unit='km^2') except -1:
|
||||
cdef:
|
||||
double area
|
||||
|
||||
check_for_error(
|
||||
h3lib.getHexagonAreaAvgKm2(resolution, &area)
|
||||
)
|
||||
|
||||
# todo: multiple units
|
||||
convert = {
|
||||
'km^2': 1.0,
|
||||
'm^2': 1000*1000.0
|
||||
}
|
||||
|
||||
try:
|
||||
area *= convert[unit]
|
||||
except:
|
||||
raise ValueError('Unknown unit: {}'.format(unit))
|
||||
|
||||
return area
|
||||
|
||||
|
||||
cpdef double cell_area(H3int h, unit='km^2') except -1:
|
||||
cdef:
|
||||
double area
|
||||
|
||||
if unit == 'rads^2':
|
||||
err = h3lib.cellAreaRads2(h, &area)
|
||||
elif unit == 'km^2':
|
||||
err = h3lib.cellAreaKm2(h, &area)
|
||||
elif unit == 'm^2':
|
||||
err = h3lib.cellAreaM2(h, &area)
|
||||
else:
|
||||
raise ValueError('Unknown unit: {}'.format(unit))
|
||||
|
||||
check_for_error(err)
|
||||
|
||||
return area
|
||||
|
||||
|
||||
cdef _could_not_find_line(err, start, end):
|
||||
msg = "Couldn't find line between cells {} and {}"
|
||||
msg = msg.format(hex(start), hex(end))
|
||||
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
cpdef H3int[:] grid_path_cells(H3int start, H3int end):
|
||||
cdef:
|
||||
int64_t n
|
||||
|
||||
# todo: can we segfault here with invalid inputs?
|
||||
# Can we trust the c library to validate the start/end cells?
|
||||
# probably applies to all size/work pairs of functions...
|
||||
err = h3lib.gridPathCellsSize(start, end, &n)
|
||||
|
||||
_could_not_find_line(err, start, end)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
err = h3lib.gridPathCells(start, end, hmm.ptr)
|
||||
|
||||
_could_not_find_line(err, start, end)
|
||||
|
||||
# todo: probably here too?
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
cpdef bool is_res_class_iii(H3int h):
|
||||
return h3lib.isResClassIII(h) == 1
|
||||
|
||||
|
||||
cpdef H3int[:] get_pentagons(int res):
|
||||
n = h3lib.pentagonCount()
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.getPentagons(res, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
cpdef H3int[:] get_res0_cells():
|
||||
n = h3lib.res0CellCount()
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.getRes0Cells(hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
# oh, this is returning a set??
|
||||
# todo: convert to int[:]?
|
||||
cpdef get_icosahedron_faces(H3int h):
|
||||
cdef:
|
||||
int n
|
||||
int[:] faces ## todo: weird, this needs to be specified to avoid errors. cython bug?
|
||||
|
||||
check_for_error(
|
||||
h3lib.maxFaceCount(h, &n)
|
||||
)
|
||||
|
||||
faces = int_mv(n)
|
||||
check_for_error(
|
||||
h3lib.getIcosahedronFaces(h, &faces[0])
|
||||
)
|
||||
|
||||
# todo: wait? do faces start from 0 or 1?
|
||||
# we could do this check/processing in the int_mv object
|
||||
out = [f for f in faces if f >= 0]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
cpdef (int, int) cell_to_local_ij(H3int origin, H3int h) except *:
|
||||
cdef:
|
||||
h3lib.CoordIJ c
|
||||
|
||||
err = h3lib.cellToLocalIj(origin, h, 0, &c)
|
||||
if err:
|
||||
msg = "Couldn't find local (i,j) between cells {} and {}."
|
||||
msg = msg.format(hex(origin), hex(h))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return c.i, c.j
|
||||
|
||||
cpdef H3int local_ij_to_cell(H3int origin, int i, int j) except 0:
|
||||
cdef:
|
||||
h3lib.CoordIJ c
|
||||
H3int out
|
||||
|
||||
c.i, c.j = i, j
|
||||
|
||||
err = h3lib.localIjToCell(origin, &c, 0, &out)
|
||||
if err:
|
||||
msg = "Couldn't find cell at local ({},{}) from cell {}."
|
||||
msg = msg.format(i, j, hex(origin))
|
||||
check_for_error_msg(err, msg)
|
||||
|
||||
return out
|
||||
Binary file not shown.
-11
@@ -1,11 +0,0 @@
|
||||
from .h3lib cimport bool, H3int
|
||||
|
||||
cpdef bool are_neighbor_cells(H3int h1, H3int h2)
|
||||
cpdef H3int cells_to_directed_edge(H3int origin, H3int destination) except *
|
||||
cpdef bool is_valid_directed_edge(H3int e)
|
||||
cpdef H3int get_directed_edge_origin(H3int e) except 1
|
||||
cpdef H3int get_directed_edge_destination(H3int e) except 1
|
||||
cpdef (H3int, H3int) directed_edge_to_cells(H3int e) except *
|
||||
cpdef H3int[:] origin_to_directed_edges(H3int origin)
|
||||
cpdef double average_hexagon_edge_length(int resolution, unit=*) except -1
|
||||
cpdef double edge_length(H3int e, unit=*) except -1
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
cimport h3lib
|
||||
from .h3lib cimport bool, H3int
|
||||
|
||||
from .error_system cimport check_for_error
|
||||
|
||||
from .memory cimport H3MemoryManager
|
||||
|
||||
# todo: make bint
|
||||
cpdef bool are_neighbor_cells(H3int h1, H3int h2):
|
||||
cdef:
|
||||
int out
|
||||
|
||||
err = h3lib.areNeighborCells(h1, h2, &out)
|
||||
|
||||
# note: we are intentionally not raising an error here, and just
|
||||
# returning false.
|
||||
# todo: is this choice consistent across the Python and C libs?
|
||||
if err:
|
||||
return False
|
||||
|
||||
return out == 1
|
||||
|
||||
|
||||
cpdef H3int cells_to_directed_edge(H3int origin, H3int destination) except *:
|
||||
cdef:
|
||||
int neighbor_out
|
||||
H3int out
|
||||
|
||||
check_for_error(
|
||||
h3lib.cellsToDirectedEdge(origin, destination, &out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
cpdef bool is_valid_directed_edge(H3int e):
|
||||
return h3lib.isValidDirectedEdge(e) == 1
|
||||
|
||||
cpdef H3int get_directed_edge_origin(H3int e) except 1:
|
||||
cdef:
|
||||
H3int out
|
||||
|
||||
check_for_error(
|
||||
h3lib.getDirectedEdgeOrigin(e, &out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
cpdef H3int get_directed_edge_destination(H3int e) except 1:
|
||||
cdef:
|
||||
H3int out
|
||||
|
||||
check_for_error(
|
||||
h3lib.getDirectedEdgeDestination(e, &out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
cpdef (H3int, H3int) directed_edge_to_cells(H3int e) except *:
|
||||
# todo: use directed_edge_to_cells in h3lib
|
||||
return get_directed_edge_origin(e), get_directed_edge_destination(e)
|
||||
|
||||
cpdef H3int[:] origin_to_directed_edges(H3int origin):
|
||||
""" Returns the 6 (or 5 for pentagons) directed edges
|
||||
for the given origin cell
|
||||
"""
|
||||
|
||||
hmm = H3MemoryManager(6)
|
||||
check_for_error(
|
||||
h3lib.originToDirectedEdges(origin, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
cpdef double average_hexagon_edge_length(int resolution, unit='km') except -1:
|
||||
cdef:
|
||||
double length
|
||||
|
||||
check_for_error(
|
||||
h3lib.getHexagonEdgeLengthAvgKm(resolution, &length)
|
||||
)
|
||||
|
||||
# todo: multiple units
|
||||
convert = {
|
||||
'km': 1.0,
|
||||
'm': 1000.0
|
||||
}
|
||||
|
||||
try:
|
||||
length *= convert[unit]
|
||||
except:
|
||||
raise ValueError('Unknown unit: {}'.format(unit))
|
||||
|
||||
return length
|
||||
|
||||
|
||||
cpdef double edge_length(H3int e, unit='km') except -1:
|
||||
cdef:
|
||||
double length
|
||||
|
||||
if unit == 'rads':
|
||||
err = h3lib.edgeLengthRads(e, &length)
|
||||
elif unit == 'km':
|
||||
err = h3lib.edgeLengthKm(e, &length)
|
||||
elif unit == 'm':
|
||||
err = h3lib.edgeLengthM(e, &length)
|
||||
else:
|
||||
raise ValueError('Unknown unit: {}'.format(unit))
|
||||
|
||||
check_for_error(err)
|
||||
|
||||
return length
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
from .h3lib cimport H3Error
|
||||
cdef check_for_error(H3Error err)
|
||||
cdef check_for_error_msg(H3Error err, str msg)
|
||||
-231
@@ -1,231 +0,0 @@
|
||||
"""
|
||||
Exceptions from the h3-py library have three possible sources:
|
||||
|
||||
- the Python code
|
||||
- the Cython code
|
||||
- the underlying H3 C library code
|
||||
|
||||
The Python and Cython `h3-py` code will only raise standard Python
|
||||
built-in exceptions; **no custom** exception classes will be used.
|
||||
|
||||
Conversely, many functions in the H3 C library return a `uint32_t`
|
||||
error code (aliased as type `H3Error`).
|
||||
When these errors happen (and `h3-py` can't recover from them internally),
|
||||
they are passed up to the Python/Cython code, where their
|
||||
`uint32_t` error values are converted to **custom** Python exception types.
|
||||
These custom exception classes all inherit from `H3BaseException`.
|
||||
|
||||
There is a 1-1 correspondence between the concrete subclasses of
|
||||
`H3BaseException` and the H3 C library `H3ErrorCodes` values.
|
||||
The correspondence is intentional, so that the user can refer to the
|
||||
H3 C library documentation on these errors.
|
||||
|
||||
The (`uint32_t` <-> Exception) correspondence should be clear from
|
||||
the names of each error/exception, but the explicit mapping is given by
|
||||
a dictionary in the code below.
|
||||
|
||||
Note that some "abstract" subclasses of `H3BaseException` are also included to
|
||||
group the exceptions by type. (We say "abstract" because Python has no easy
|
||||
way to make true abstract exception classes.)
|
||||
|
||||
These "abstract" exceptions will never be raised directly by `h3-py`, but they
|
||||
allow the user to catch general groups of errors.
|
||||
Note that `h3-py` will only ever directly raise
|
||||
the "concrete" exception classes.
|
||||
|
||||
Summarizing, all exceptions originating from the C library inherit from
|
||||
`H3BaseException`, which has both "abstract" and "concrete" subclasses.
|
||||
|
||||
**Abstract classes**:
|
||||
|
||||
- H3BaseException
|
||||
- H3ValueError
|
||||
- H3MemoryError
|
||||
- H3GridNavigationError
|
||||
|
||||
**Concrete classes**:
|
||||
|
||||
- H3FailedError
|
||||
- H3DomainError
|
||||
- H3LatLngDomainError
|
||||
- H3ResDomainError
|
||||
- H3CellInvalidError
|
||||
- H3DirEdgeInvalidError
|
||||
- H3UndirEdgeInvalidError
|
||||
- H3VertexInvalidError
|
||||
- H3PentagonError
|
||||
- H3DuplicateInputError
|
||||
- H3NotNeighborsError
|
||||
- H3ResMismatchError
|
||||
- H3MemoryAllocError
|
||||
- H3MemoryBoundsError
|
||||
- H3OptionInvalidError
|
||||
|
||||
|
||||
# TODO: add tests verifying that concrete exception classes have the right error codes associated with them
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from .h3lib cimport (
|
||||
H3Error,
|
||||
|
||||
# H3ErrorCodes enum values
|
||||
E_SUCCESS,
|
||||
E_FAILED,
|
||||
E_DOMAIN,
|
||||
E_LATLNG_DOMAIN,
|
||||
E_RES_DOMAIN,
|
||||
E_CELL_INVALID,
|
||||
E_DIR_EDGE_INVALID,
|
||||
E_UNDIR_EDGE_INVALID,
|
||||
E_VERTEX_INVALID,
|
||||
E_PENTAGON,
|
||||
E_DUPLICATE_INPUT,
|
||||
E_NOT_NEIGHBORS,
|
||||
E_RES_MISMATCH,
|
||||
E_MEMORY_ALLOC,
|
||||
E_MEMORY_BOUNDS,
|
||||
E_OPTION_INVALID,
|
||||
)
|
||||
|
||||
@contextmanager
|
||||
def _the_error(obj):
|
||||
"""
|
||||
Syntactic maple syrup for grouping exception definitions.
|
||||
The associated `with` statement ends up as a not-half-bad
|
||||
approximation to a valid sentence fragment.
|
||||
|
||||
This provides sort of a "pretend scope", in that it allows for
|
||||
block indentation which helps to visually indicate the "scope"
|
||||
of the `... as e` statement. Just note that Python doesn't treat the
|
||||
`with` block as a "true" separate scope.
|
||||
|
||||
Note that this doesn't actually do anything context-manager-y, outside
|
||||
of the variable assignment and block indentation.
|
||||
"""
|
||||
yield obj
|
||||
|
||||
|
||||
#
|
||||
# Base exception for C library error codes
|
||||
#
|
||||
class H3BaseException(Exception):
|
||||
""" Base H3 exception class.
|
||||
|
||||
Concrete subclasses of this class correspond to specific
|
||||
error codes from the C library.
|
||||
|
||||
Base/abstract subclasses will have `h3_error_code = None`, while
|
||||
concrete subclasses will have `h3_error_code` equal to their associated
|
||||
C library error code.
|
||||
"""
|
||||
h3_error_code = None
|
||||
|
||||
|
||||
#
|
||||
# A few "abstract" exceptions; organizational.
|
||||
#
|
||||
with _the_error(H3BaseException) as e:
|
||||
class H3ValueError(e, ValueError): ...
|
||||
class H3MemoryError(e, MemoryError): ...
|
||||
class H3GridNavigationError(e, RuntimeError): ...
|
||||
|
||||
|
||||
#
|
||||
# Concrete exceptions
|
||||
#
|
||||
class UnknownH3ErrorCode(H3BaseException):
|
||||
"""
|
||||
Indicates that the h3-py Python bindings have received an
|
||||
unrecognized error code from the C library.
|
||||
|
||||
This should never happen. Please report if you get this error.
|
||||
|
||||
Note that this exception is *outside* of the
|
||||
H3BaseException class hierarchy.
|
||||
"""
|
||||
pass
|
||||
|
||||
with _the_error(H3BaseException) as e:
|
||||
class H3FailedError(e): ...
|
||||
|
||||
with _the_error(H3GridNavigationError) as e:
|
||||
class H3PentagonError(e): ...
|
||||
|
||||
with _the_error(H3MemoryError) as e:
|
||||
class H3MemoryAllocError(e): ...
|
||||
class H3MemoryBoundsError(e): ...
|
||||
|
||||
with _the_error(H3ValueError) as e:
|
||||
class H3DomainError(e): ...
|
||||
class H3LatLngDomainError(e): ...
|
||||
class H3ResDomainError(e): ...
|
||||
class H3CellInvalidError(e): ...
|
||||
class H3DirEdgeInvalidError(e): ...
|
||||
class H3UndirEdgeInvalidError(e): ...
|
||||
class H3VertexInvalidError(e): ...
|
||||
class H3DuplicateInputError(e): ...
|
||||
class H3NotNeighborsError(e): ...
|
||||
class H3ResMismatchError(e): ...
|
||||
class H3OptionInvalidError(e): ...
|
||||
|
||||
|
||||
"""
|
||||
This defines a mapping between uint32_t error codes and concrete Python
|
||||
exception classes.
|
||||
Note that we intentionally omit E_SUCCESS, as it isn't an actual error.
|
||||
"""
|
||||
error_mapping = {
|
||||
E_FAILED: H3FailedError,
|
||||
E_DOMAIN: H3DomainError,
|
||||
E_LATLNG_DOMAIN: H3LatLngDomainError,
|
||||
E_RES_DOMAIN: H3ResDomainError,
|
||||
E_CELL_INVALID: H3CellInvalidError,
|
||||
E_DIR_EDGE_INVALID: H3DirEdgeInvalidError,
|
||||
E_UNDIR_EDGE_INVALID: H3UndirEdgeInvalidError,
|
||||
E_VERTEX_INVALID: H3VertexInvalidError,
|
||||
E_PENTAGON: H3PentagonError,
|
||||
E_DUPLICATE_INPUT: H3DuplicateInputError,
|
||||
E_NOT_NEIGHBORS: H3NotNeighborsError,
|
||||
E_RES_MISMATCH: H3ResMismatchError,
|
||||
E_MEMORY_ALLOC: H3MemoryAllocError,
|
||||
E_MEMORY_BOUNDS: H3MemoryBoundsError,
|
||||
E_OPTION_INVALID: H3OptionInvalidError,
|
||||
}
|
||||
|
||||
# Go back and modify the class definitions so that each concrete exception
|
||||
# stores its associated error code.
|
||||
for code, ex in error_mapping.items():
|
||||
ex.h3_error_code = code
|
||||
|
||||
|
||||
#
|
||||
# Helper functions
|
||||
#
|
||||
|
||||
# TODO: Move the helpers to util?
|
||||
# TODO: Unclear how/where to expose these functions. cdef/cpdef?
|
||||
|
||||
cdef code_to_exception(H3Error err):
|
||||
if err == E_SUCCESS:
|
||||
return None
|
||||
elif err in error_mapping:
|
||||
return error_mapping[err]
|
||||
else:
|
||||
raise UnknownH3ErrorCode(err)
|
||||
|
||||
cdef check_for_error(H3Error err):
|
||||
ex = code_to_exception(err)
|
||||
if ex:
|
||||
raise ex
|
||||
|
||||
# todo: There's no easy way to do `*args` in `cdef` functions, but I'm also
|
||||
# not sure this even needs to be a Cython `cdef` function at all, or that
|
||||
# any of the other helper functions need to be in Cython.
|
||||
# todo: Revisit after we've played with this a bit.
|
||||
# todo: also: maybe the extra messages aren't that much more helpful...
|
||||
cdef check_for_error_msg(H3Error err, str msg):
|
||||
ex = code_to_exception(err)
|
||||
if ex:
|
||||
raise ex(msg)
|
||||
Vendored
-809
@@ -1,809 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2021 Uber Technologies, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/** @file h3api.h
|
||||
* @brief Primary H3 core library entry points.
|
||||
*
|
||||
* This file defines the public API of the H3 library. Incompatible changes to
|
||||
* these functions require the library's major version be increased.
|
||||
*/
|
||||
|
||||
#ifndef H3API_H
|
||||
#define H3API_H
|
||||
|
||||
/*
|
||||
* Preprocessor code to support renaming (prefixing) the public API.
|
||||
* All public functions should be wrapped in H3_EXPORT so they can be
|
||||
* renamed.
|
||||
*/
|
||||
#ifdef H3_PREFIX
|
||||
#define XTJOIN(a, b) a##b
|
||||
#define TJOIN(a, b) XTJOIN(a, b)
|
||||
|
||||
/* export joins the user provided prefix with our exported function name */
|
||||
#define H3_EXPORT(name) TJOIN(H3_PREFIX, name)
|
||||
#else
|
||||
#define H3_EXPORT(name) name
|
||||
#endif
|
||||
|
||||
/* Windows DLL requires attributes indicating what to export */
|
||||
#if _WIN32 && BUILD_SHARED_LIBS
|
||||
#if BUILDING_H3
|
||||
#define DECLSPEC __declspec(dllexport)
|
||||
#else
|
||||
#define DECLSPEC __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define DECLSPEC
|
||||
#endif
|
||||
|
||||
/* For uint64_t */
|
||||
#include <stdint.h>
|
||||
/* For size_t */
|
||||
#include <stdlib.h>
|
||||
|
||||
/*
|
||||
* H3 is compiled as C, not C++ code. `extern "C"` is needed for C++ code
|
||||
* to be able to use the library.
|
||||
*/
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief Identifier for an object (cell, edge, etc) in the H3 system.
|
||||
*
|
||||
* The H3Index fits within a 64-bit unsigned integer.
|
||||
*/
|
||||
typedef uint64_t H3Index;
|
||||
|
||||
/**
|
||||
* Invalid index used to indicate an error from latLngToCell and related
|
||||
* functions or missing data in arrays of H3 indices. Analogous to NaN in
|
||||
* floating point.
|
||||
*/
|
||||
#define H3_NULL 0
|
||||
|
||||
/** @brief Result code (success or specific error) from an H3 operation */
|
||||
typedef uint32_t H3Error;
|
||||
|
||||
typedef enum {
|
||||
E_SUCCESS = 0, // Success (no error)
|
||||
E_FAILED =
|
||||
1, // The operation failed but a more specific error is not available
|
||||
E_DOMAIN = 2, // Argument was outside of acceptable range (when a more
|
||||
// specific error code is not available)
|
||||
E_LATLNG_DOMAIN =
|
||||
3, // Latitude or longitude arguments were outside of acceptable range
|
||||
E_RES_DOMAIN = 4, // Resolution argument was outside of acceptable range
|
||||
E_CELL_INVALID = 5, // `H3Index` cell argument was not valid
|
||||
E_DIR_EDGE_INVALID = 6, // `H3Index` directed edge argument was not valid
|
||||
E_UNDIR_EDGE_INVALID =
|
||||
7, // `H3Index` undirected edge argument was not valid
|
||||
E_VERTEX_INVALID = 8, // `H3Index` vertex argument was not valid
|
||||
E_PENTAGON = 9, // Pentagon distortion was encountered which the algorithm
|
||||
// could not handle it
|
||||
E_DUPLICATE_INPUT = 10, // Duplicate input was encountered in the arguments
|
||||
// and the algorithm could not handle it
|
||||
E_NOT_NEIGHBORS = 11, // `H3Index` cell arguments were not neighbors
|
||||
E_RES_MISMATCH =
|
||||
12, // `H3Index` cell arguments had incompatible resolutions
|
||||
E_MEMORY_ALLOC = 13, // Necessary memory allocation failed
|
||||
E_MEMORY_BOUNDS = 14, // Bounds of provided memory were not large enough
|
||||
E_OPTION_INVALID = 15 // Mode or flags argument was not valid.
|
||||
} H3ErrorCodes;
|
||||
|
||||
/** @defgroup describeH3Error describeH3Error
|
||||
* Functions for describeH3Error
|
||||
* @{
|
||||
*/
|
||||
/** @brief converts the provided H3Error value into a description string */
|
||||
DECLSPEC const char *H3_EXPORT(describeH3Error)(H3Error err);
|
||||
/** @} */
|
||||
|
||||
/* library version numbers generated from VERSION file */
|
||||
// clang-format off
|
||||
#define H3_VERSION_MAJOR 4
|
||||
#define H3_VERSION_MINOR 3
|
||||
#define H3_VERSION_PATCH 0
|
||||
// clang-format on
|
||||
|
||||
/** Maximum number of cell boundary vertices; worst case is pentagon:
|
||||
* 5 original verts + 5 edge crossings
|
||||
*/
|
||||
#define MAX_CELL_BNDRY_VERTS 10
|
||||
|
||||
/** @struct LatLng
|
||||
@brief latitude/longitude in radians
|
||||
*/
|
||||
typedef struct {
|
||||
double lat; ///< latitude in radians
|
||||
double lng; ///< longitude in radians
|
||||
} LatLng;
|
||||
|
||||
/** @struct CellBoundary
|
||||
@brief cell boundary in latitude/longitude
|
||||
*/
|
||||
typedef struct {
|
||||
int numVerts; ///< number of vertices
|
||||
LatLng verts[MAX_CELL_BNDRY_VERTS]; ///< vertices in ccw order
|
||||
} CellBoundary;
|
||||
|
||||
/** @struct GeoLoop
|
||||
* @brief similar to CellBoundary, but requires more alloc work
|
||||
*/
|
||||
typedef struct {
|
||||
int numVerts;
|
||||
LatLng *verts;
|
||||
} GeoLoop;
|
||||
|
||||
/** @struct GeoPolygon
|
||||
* @brief Simplified core of GeoJSON Polygon coordinates definition
|
||||
*/
|
||||
typedef struct {
|
||||
GeoLoop geoloop; ///< exterior boundary of the polygon
|
||||
int numHoles; ///< number of elements in the array pointed to by holes
|
||||
GeoLoop *holes; ///< interior boundaries (holes) in the polygon
|
||||
} GeoPolygon;
|
||||
|
||||
/** @struct GeoMultiPolygon
|
||||
* @brief Simplified core of GeoJSON MultiPolygon coordinates definition
|
||||
*/
|
||||
typedef struct {
|
||||
int numPolygons;
|
||||
GeoPolygon *polygons;
|
||||
} GeoMultiPolygon;
|
||||
|
||||
/**
|
||||
* Values representing polyfill containment modes, to be used in
|
||||
* the `flags` bit field for `polygonToCellsExperimental`.
|
||||
*/
|
||||
typedef enum {
|
||||
CONTAINMENT_CENTER = 0, ///< Cell center is contained in the shape
|
||||
CONTAINMENT_FULL = 1, ///< Cell is fully contained in the shape
|
||||
CONTAINMENT_OVERLAPPING = 2, ///< Cell overlaps the shape at any point
|
||||
CONTAINMENT_OVERLAPPING_BBOX = 3, ///< Cell bounding box overlaps shape
|
||||
CONTAINMENT_INVALID = 4 ///< This mode is invalid and should not be used
|
||||
} ContainmentMode;
|
||||
|
||||
/** @struct LinkedLatLng
|
||||
* @brief A coordinate node in a linked geo structure, part of a linked list
|
||||
*/
|
||||
typedef struct LinkedLatLng LinkedLatLng;
|
||||
struct LinkedLatLng {
|
||||
LatLng vertex;
|
||||
LinkedLatLng *next;
|
||||
};
|
||||
|
||||
/** @struct LinkedGeoLoop
|
||||
* @brief A loop node in a linked geo structure, part of a linked list
|
||||
*/
|
||||
typedef struct LinkedGeoLoop LinkedGeoLoop;
|
||||
struct LinkedGeoLoop {
|
||||
LinkedLatLng *first;
|
||||
LinkedLatLng *last;
|
||||
LinkedGeoLoop *next;
|
||||
};
|
||||
|
||||
/** @struct LinkedGeoPolygon
|
||||
* @brief A polygon node in a linked geo structure, part of a linked list.
|
||||
*/
|
||||
typedef struct LinkedGeoPolygon LinkedGeoPolygon;
|
||||
struct LinkedGeoPolygon {
|
||||
LinkedGeoLoop *first;
|
||||
LinkedGeoLoop *last;
|
||||
LinkedGeoPolygon *next;
|
||||
};
|
||||
|
||||
/** @struct CoordIJ
|
||||
* @brief IJ hexagon coordinates
|
||||
*
|
||||
* Each axis is spaced 120 degrees apart.
|
||||
*/
|
||||
typedef struct {
|
||||
int i; ///< i component
|
||||
int j; ///< j component
|
||||
} CoordIJ;
|
||||
|
||||
/** @defgroup latLngToCell latLngToCell
|
||||
* Functions for latLngToCell
|
||||
* @{
|
||||
*/
|
||||
/** @brief find the H3 index of the resolution res cell containing the lat/lng
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToLatLng cellToLatLng
|
||||
* Functions for cellToLatLng
|
||||
* @{
|
||||
*/
|
||||
/** @brief find the lat/lng center point g of the cell h3 */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToBoundary cellToBoundary
|
||||
* Functions for cellToBoundary
|
||||
* @{
|
||||
*/
|
||||
/** @brief give the cell boundary in lat/lng coordinates for the cell h3 */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *gp);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup gridDisk gridDisk
|
||||
* Functions for gridDisk
|
||||
* @{
|
||||
*/
|
||||
/** @brief maximum number of hexagons in k-ring */
|
||||
DECLSPEC H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out);
|
||||
|
||||
/** @brief hexagons neighbors in all directions, assuming no pentagons */
|
||||
DECLSPEC H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @brief hexagons neighbors in all directions, assuming no pentagons,
|
||||
* reporting distance from origin */
|
||||
DECLSPEC H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k,
|
||||
H3Index *out,
|
||||
int *distances);
|
||||
|
||||
/** @brief hexagons neighbors in all directions reporting distance from origin
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k,
|
||||
H3Index *out, int *distances);
|
||||
|
||||
/** @brief collection of hex rings sorted by ring for all given hexagons */
|
||||
DECLSPEC H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k,
|
||||
H3Index *out);
|
||||
|
||||
/** @brief hexagon neighbors in all directions */
|
||||
DECLSPEC H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup gridDiskDistances gridDiskDistances
|
||||
* Functions for gridDiskDistances
|
||||
* @{
|
||||
*/
|
||||
/** @brief hexagon neighbors in all directions, reporting distance from origin
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k,
|
||||
H3Index *out, int *distances);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup gridRing gridRing
|
||||
* Functions for gridRing
|
||||
* @{
|
||||
*/
|
||||
/** @brief maximum number of hexagons in hollow k-ring */
|
||||
DECLSPEC H3Error H3_EXPORT(maxGridRingSize)(int k, int64_t *out);
|
||||
|
||||
/** @brief hollow hexagon ring k distance from origin */
|
||||
DECLSPEC H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out);
|
||||
|
||||
/** @brief hollow hexagon ring k distance from origin */
|
||||
DECLSPEC H3Error H3_EXPORT(gridRing)(H3Index origin, int k, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup polygonToCells polygonToCells
|
||||
* Functions for polygonToCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief maximum number of cells that could be in the polygon */
|
||||
DECLSPEC H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon,
|
||||
int res, uint32_t flags,
|
||||
int64_t *out);
|
||||
|
||||
/** @brief cells within the given polygon */
|
||||
DECLSPEC H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon,
|
||||
int res, uint32_t flags,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup polygonToCellsExperimental polygonToCellsExperimental
|
||||
* Functions for polygonToCellsExperimental.
|
||||
* This is an experimental-only API and is subject to change in minor versions.
|
||||
* @{
|
||||
*/
|
||||
/** @brief maximum number of cells that could be in the polygon */
|
||||
DECLSPEC H3Error H3_EXPORT(maxPolygonToCellsSizeExperimental)(
|
||||
const GeoPolygon *polygon, int res, uint32_t flags, int64_t *out);
|
||||
|
||||
/** @brief cells within the given polygon */
|
||||
DECLSPEC H3Error H3_EXPORT(polygonToCellsExperimental)(
|
||||
const GeoPolygon *polygon, int res, uint32_t flags, int64_t size,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellsToMultiPolygon cellsToMultiPolygon
|
||||
* Functions for cellsToMultiPolygon (currently a binding-only concept)
|
||||
* @{
|
||||
*/
|
||||
/** @brief Create a LinkedGeoPolygon from a set of contiguous hexagons */
|
||||
DECLSPEC H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set,
|
||||
const int numHexes,
|
||||
LinkedGeoPolygon *out);
|
||||
|
||||
/** @brief Free all memory created for a LinkedGeoPolygon */
|
||||
DECLSPEC void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup degsToRads degsToRads
|
||||
* Functions for degsToRads
|
||||
* @{
|
||||
*/
|
||||
/** @brief converts degrees to radians */
|
||||
DECLSPEC double H3_EXPORT(degsToRads)(double degrees);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup radsToDegs radsToDegs
|
||||
* Functions for radsToDegs
|
||||
* @{
|
||||
*/
|
||||
/** @brief converts radians to degrees */
|
||||
DECLSPEC double H3_EXPORT(radsToDegs)(double radians);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup greatCircleDistance greatCircleDistance
|
||||
* Functions for distance
|
||||
* @{
|
||||
*/
|
||||
/** @brief "great circle distance" between pairs of LatLng points in radians*/
|
||||
DECLSPEC double H3_EXPORT(greatCircleDistanceRads)(const LatLng *a,
|
||||
const LatLng *b);
|
||||
|
||||
/** @brief "great circle distance" between pairs of LatLng points in
|
||||
* kilometers*/
|
||||
DECLSPEC double H3_EXPORT(greatCircleDistanceKm)(const LatLng *a,
|
||||
const LatLng *b);
|
||||
|
||||
/** @brief "great circle distance" between pairs of LatLng points in meters*/
|
||||
DECLSPEC double H3_EXPORT(greatCircleDistanceM)(const LatLng *a,
|
||||
const LatLng *b);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getHexagonAreaAvg getHexagonAreaAvg
|
||||
* Functions for getHexagonAreaAvg
|
||||
* @{
|
||||
*/
|
||||
/** @brief average hexagon area in square kilometers (excludes pentagons) */
|
||||
DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgKm2)(int res, double *out);
|
||||
|
||||
/** @brief average hexagon area in square meters (excludes pentagons) */
|
||||
DECLSPEC H3Error H3_EXPORT(getHexagonAreaAvgM2)(int res, double *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellArea cellArea
|
||||
* Functions for cellArea
|
||||
* @{
|
||||
*/
|
||||
/** @brief exact area for a specific cell (hexagon or pentagon) in radians^2 */
|
||||
DECLSPEC H3Error H3_EXPORT(cellAreaRads2)(H3Index h, double *out);
|
||||
|
||||
/** @brief exact area for a specific cell (hexagon or pentagon) in kilometers^2
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(cellAreaKm2)(H3Index h, double *out);
|
||||
|
||||
/** @brief exact area for a specific cell (hexagon or pentagon) in meters^2 */
|
||||
DECLSPEC H3Error H3_EXPORT(cellAreaM2)(H3Index h, double *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getHexagonEdgeLengthAvg getHexagonEdgeLengthAvg
|
||||
* Functions for getHexagonEdgeLengthAvg
|
||||
* @{
|
||||
*/
|
||||
/** @brief average hexagon edge length in kilometers (excludes pentagons) */
|
||||
DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgKm)(int res, double *out);
|
||||
|
||||
/** @brief average hexagon edge length in meters (excludes pentagons) */
|
||||
DECLSPEC H3Error H3_EXPORT(getHexagonEdgeLengthAvgM)(int res, double *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup edgeLength edgeLength
|
||||
* Functions for edgeLength
|
||||
* @{
|
||||
*/
|
||||
/** @brief exact length for a specific directed edge in radians*/
|
||||
DECLSPEC H3Error H3_EXPORT(edgeLengthRads)(H3Index edge, double *length);
|
||||
|
||||
/** @brief exact length for a specific directed edge in kilometers*/
|
||||
DECLSPEC H3Error H3_EXPORT(edgeLengthKm)(H3Index edge, double *length);
|
||||
|
||||
/** @brief exact length for a specific directed edge in meters*/
|
||||
DECLSPEC H3Error H3_EXPORT(edgeLengthM)(H3Index edge, double *length);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getNumCells getNumCells
|
||||
* Functions for getNumCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief number of cells (hexagons and pentagons) for a given resolution
|
||||
*
|
||||
* It works out to be `2 + 120*7^r` for resolution `r`.
|
||||
*
|
||||
* # Mathematical notes
|
||||
*
|
||||
* Let h(n) be the number of children n levels below
|
||||
* a single *hexagon*.
|
||||
*
|
||||
* Then h(n) = 7^n.
|
||||
*
|
||||
* Let p(n) be the number of children n levels below
|
||||
* a single *pentagon*.
|
||||
*
|
||||
* Then p(0) = 1, and p(1) = 6, since each pentagon
|
||||
* has 5 hexagonal immediate children and 1 pentagonal
|
||||
* immediate child.
|
||||
*
|
||||
* In general, we have the recurrence relation
|
||||
*
|
||||
* p(n) = 5*h(n-1) + p(n-1)
|
||||
* = 5*7^(n-1) + p(n-1).
|
||||
*
|
||||
* Working through the recurrence, we get that
|
||||
*
|
||||
* p(n) = 1 + 5*\sum_{k=1}^n 7^{k-1}
|
||||
* = 1 + 5*(7^n - 1)/6,
|
||||
*
|
||||
* using the closed form for a geometric series.
|
||||
*
|
||||
* Using the closed forms for h(n) and p(n), we can
|
||||
* get a closed form for the total number of cells
|
||||
* at resolution r:
|
||||
*
|
||||
* c(r) = 12*p(r) + 110*h(r)
|
||||
* = 2 + 120*7^r.
|
||||
*
|
||||
*
|
||||
* @param res H3 cell resolution
|
||||
*
|
||||
* @return number of cells at resolution `res`
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(getNumCells)(int res, int64_t *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getRes0Cells getRes0Cells
|
||||
* Functions for getRes0Cells
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the number of resolution 0 cells (hexagons and pentagons) */
|
||||
DECLSPEC int H3_EXPORT(res0CellCount)(void);
|
||||
|
||||
/** @brief provides all base cells in H3Index format*/
|
||||
DECLSPEC H3Error H3_EXPORT(getRes0Cells)(H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getPentagons getPentagons
|
||||
* Functions for getPentagons
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the number of pentagons per resolution */
|
||||
DECLSPEC int H3_EXPORT(pentagonCount)(void);
|
||||
|
||||
/** @brief generates all pentagons at the specified resolution */
|
||||
DECLSPEC H3Error H3_EXPORT(getPentagons)(int res, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getResolution getResolution
|
||||
* Functions for getResolution
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the resolution of the provided H3 index
|
||||
* Works on both cells and directed edges. */
|
||||
DECLSPEC int H3_EXPORT(getResolution)(H3Index h);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getBaseCellNumber getBaseCellNumber
|
||||
* Functions for getBaseCellNumber
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the base cell "number" (0 to 121) of the provided H3 cell
|
||||
*
|
||||
* Note: Technically works on H3 edges, but will return base cell of the
|
||||
* origin cell. */
|
||||
DECLSPEC int H3_EXPORT(getBaseCellNumber)(H3Index h);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup stringToH3 stringToH3
|
||||
* Functions for stringToH3
|
||||
* @{
|
||||
*/
|
||||
/** @brief converts the canonical string format to H3Index format */
|
||||
DECLSPEC H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup h3ToString h3ToString
|
||||
* Functions for h3ToString
|
||||
* @{
|
||||
*/
|
||||
/** @brief converts an H3Index to a canonical string */
|
||||
DECLSPEC H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup isValidCell isValidCell
|
||||
* Functions for isValidCell
|
||||
* @{
|
||||
*/
|
||||
/** @brief confirms if an H3Index is a valid cell (hexagon or pentagon)
|
||||
* In particular, returns 0 (False) for H3 directed edges or invalid data
|
||||
*/
|
||||
DECLSPEC int H3_EXPORT(isValidCell)(H3Index h);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToParent cellToParent
|
||||
* Functions for cellToParent
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the parent (or grandparent, etc) cell of the given cell
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes,
|
||||
H3Index *parent);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToChildren cellToChildren
|
||||
* Functions for cellToChildren
|
||||
* @{
|
||||
*/
|
||||
/** @brief determines the exact number of children (or grandchildren, etc)
|
||||
* that would be returned for the given cell */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes,
|
||||
int64_t *out);
|
||||
|
||||
/** @brief provides the children (or grandchildren, etc) of the given cell */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes,
|
||||
H3Index *children);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToCenterChild cellToCenterChild
|
||||
* Functions for cellToCenterChild
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the center child of the given cell at the specified
|
||||
* resolution */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes,
|
||||
H3Index *child);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToChildPos cellToChildPos
|
||||
* Functions for cellToChildPos
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the position of the cell within an ordered list of all
|
||||
* children of the cell's parent at the specified resolution */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToChildPos)(H3Index child, int parentRes,
|
||||
int64_t *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup childPosToCell childPosToCell
|
||||
* Functions for childPosToCell
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the child cell at a given position within an ordered list of
|
||||
* all children at the specified resolution */
|
||||
DECLSPEC H3Error H3_EXPORT(childPosToCell)(int64_t childPos, H3Index parent,
|
||||
int childRes, H3Index *child);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup compactCells compactCells
|
||||
* Functions for compactCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief compacts the given set of hexagons as best as possible */
|
||||
DECLSPEC H3Error H3_EXPORT(compactCells)(const H3Index *h3Set,
|
||||
H3Index *compactedSet,
|
||||
const int64_t numHexes);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup uncompactCells uncompactCells
|
||||
* Functions for uncompactCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief determines the exact number of hexagons that will be uncompacted
|
||||
* from the compacted set */
|
||||
DECLSPEC H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet,
|
||||
const int64_t numCompacted,
|
||||
const int res, int64_t *out);
|
||||
|
||||
/** @brief uncompacts the compacted hexagon set */
|
||||
DECLSPEC H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet,
|
||||
const int64_t numCompacted,
|
||||
H3Index *outSet,
|
||||
const int64_t numOut, const int res);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup isResClassIII isResClassIII
|
||||
* Functions for isResClassIII
|
||||
* @{
|
||||
*/
|
||||
/** @brief determines if a hexagon is Class III (or Class II) */
|
||||
DECLSPEC int H3_EXPORT(isResClassIII)(H3Index h);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup isPentagon isPentagon
|
||||
* Functions for isPentagon
|
||||
* @{
|
||||
*/
|
||||
/** @brief determines if an H3 cell is a pentagon */
|
||||
DECLSPEC int H3_EXPORT(isPentagon)(H3Index h);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getIcosahedronFaces getIcosahedronFaces
|
||||
* Functions for getIcosahedronFaces
|
||||
* @{
|
||||
*/
|
||||
/** @brief Max number of icosahedron faces intersected by an index */
|
||||
DECLSPEC H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out);
|
||||
|
||||
/** @brief Find all icosahedron faces intersected by a given H3 index */
|
||||
DECLSPEC H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup areNeighborCells areNeighborCells
|
||||
* Functions for areNeighborCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns whether or not the provided hexagons border */
|
||||
DECLSPEC H3Error H3_EXPORT(areNeighborCells)(H3Index origin,
|
||||
H3Index destination, int *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellsToDirectedEdge cellsToDirectedEdge
|
||||
* Functions for cellsToDirectedEdge
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns the directed edge H3Index for the specified origin and
|
||||
* destination */
|
||||
DECLSPEC H3Error H3_EXPORT(cellsToDirectedEdge)(H3Index origin,
|
||||
H3Index destination,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup isValidDirectedEdge isValidDirectedEdge
|
||||
* Functions for isValidDirectedEdge
|
||||
* @{
|
||||
*/
|
||||
/** @brief returns whether the H3Index is a valid directed edge */
|
||||
DECLSPEC int H3_EXPORT(isValidDirectedEdge)(H3Index edge);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getDirectedEdgeOrigin \
|
||||
* getDirectedEdgeOrigin
|
||||
* Functions for getDirectedEdgeOrigin
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the origin hexagon H3Index from the directed edge
|
||||
* H3Index */
|
||||
DECLSPEC H3Error H3_EXPORT(getDirectedEdgeOrigin)(H3Index edge, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup getDirectedEdgeDestination \
|
||||
* getDirectedEdgeDestination
|
||||
* Functions for getDirectedEdgeDestination
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the destination hexagon H3Index from the directed edge
|
||||
* H3Index */
|
||||
DECLSPEC H3Error H3_EXPORT(getDirectedEdgeDestination)(H3Index edge,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup directedEdgeToCells \
|
||||
* directedEdgeToCells
|
||||
* Functions for directedEdgeToCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the origin and destination hexagons from the directed
|
||||
* edge H3Index */
|
||||
DECLSPEC H3Error H3_EXPORT(directedEdgeToCells)(H3Index edge,
|
||||
H3Index *originDestination);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup originToDirectedEdges \
|
||||
* originToDirectedEdges
|
||||
* Functions for originToDirectedEdges
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the 6 (or 5 for pentagons) edges associated with the H3Index
|
||||
*/
|
||||
DECLSPEC H3Error H3_EXPORT(originToDirectedEdges)(H3Index origin,
|
||||
H3Index *edges);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup directedEdgeToBoundary directedEdgeToBoundary
|
||||
* Functions for directedEdgeToBoundary
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns the CellBoundary containing the coordinates of the edge */
|
||||
DECLSPEC H3Error H3_EXPORT(directedEdgeToBoundary)(H3Index edge,
|
||||
CellBoundary *gb);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToVertex cellToVertex
|
||||
* Functions for cellToVertex
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns a single vertex for a given cell, as an H3 index */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToVertex)(H3Index origin, int vertexNum,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToVertexes cellToVertexes
|
||||
* Functions for cellToVertexes
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns all vertexes for a given cell, as H3 indexes */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToVertexes)(H3Index origin, H3Index *vertexes);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup vertexToLatLng vertexToLatLng
|
||||
* Functions for vertexToLatLng
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns a single vertex for a given cell, as an H3 index */
|
||||
DECLSPEC H3Error H3_EXPORT(vertexToLatLng)(H3Index vertex, LatLng *point);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup isValidVertex isValidVertex
|
||||
* Functions for isValidVertex
|
||||
* @{
|
||||
*/
|
||||
/** @brief Whether the input is a valid H3 vertex */
|
||||
DECLSPEC int H3_EXPORT(isValidVertex)(H3Index vertex);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup gridDistance gridDistance
|
||||
* Functions for gridDistance
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns grid distance between two indexes */
|
||||
DECLSPEC H3Error H3_EXPORT(gridDistance)(H3Index origin, H3Index h3,
|
||||
int64_t *distance);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup gridPathCells gridPathCells
|
||||
* Functions for gridPathCells
|
||||
* @{
|
||||
*/
|
||||
/** @brief Number of indexes in a line connecting two indexes */
|
||||
DECLSPEC H3Error H3_EXPORT(gridPathCellsSize)(H3Index start, H3Index end,
|
||||
int64_t *size);
|
||||
|
||||
/** @brief Line of h3 indexes connecting two indexes */
|
||||
DECLSPEC H3Error H3_EXPORT(gridPathCells)(H3Index start, H3Index end,
|
||||
H3Index *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup cellToLocalIj cellToLocalIj
|
||||
* Functions for cellToLocalIj
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns two dimensional coordinates for the given index */
|
||||
DECLSPEC H3Error H3_EXPORT(cellToLocalIj)(H3Index origin, H3Index h3,
|
||||
uint32_t mode, CoordIJ *out);
|
||||
/** @} */
|
||||
|
||||
/** @defgroup localIjToCell localIjToCell
|
||||
* Functions for localIjToCell
|
||||
* @{
|
||||
*/
|
||||
/** @brief Returns index for the given two dimensional coordinates */
|
||||
DECLSPEC H3Error H3_EXPORT(localIjToCell)(H3Index origin, const CoordIJ *ij,
|
||||
uint32_t mode, H3Index *out);
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
# cython: c_string_type=unicode, c_string_encoding=utf8
|
||||
from cpython cimport bool
|
||||
from libc.stdint cimport uint32_t, uint64_t, int64_t
|
||||
|
||||
ctypedef object H3str
|
||||
|
||||
cdef extern from 'h3api.h':
|
||||
cdef int H3_VERSION_MAJOR
|
||||
cdef int H3_VERSION_MINOR
|
||||
cdef int H3_VERSION_PATCH
|
||||
|
||||
ctypedef uint64_t H3int 'H3Index'
|
||||
|
||||
ctypedef uint32_t H3Error
|
||||
ctypedef enum H3ErrorCodes:
|
||||
E_SUCCESS = 0
|
||||
E_FAILED = 1
|
||||
E_DOMAIN = 2
|
||||
E_LATLNG_DOMAIN = 3
|
||||
E_RES_DOMAIN = 4
|
||||
E_CELL_INVALID = 5
|
||||
E_DIR_EDGE_INVALID = 6
|
||||
E_UNDIR_EDGE_INVALID = 7
|
||||
E_VERTEX_INVALID = 8
|
||||
E_PENTAGON = 9
|
||||
E_DUPLICATE_INPUT = 10
|
||||
E_NOT_NEIGHBORS = 11
|
||||
E_RES_MISMATCH = 12
|
||||
E_MEMORY_ALLOC = 13
|
||||
E_MEMORY_BOUNDS = 14
|
||||
E_OPTION_INVALID = 15
|
||||
|
||||
ctypedef struct LatLng:
|
||||
double lat # in radians
|
||||
double lng # in radians
|
||||
|
||||
ctypedef struct CellBoundary:
|
||||
int num_verts 'numVerts'
|
||||
LatLng verts[10] # MAX_CELL_BNDRY_VERTS
|
||||
|
||||
ctypedef struct CoordIJ:
|
||||
int i
|
||||
int j
|
||||
|
||||
ctypedef struct LinkedLatLng:
|
||||
LatLng data 'vertex'
|
||||
LinkedLatLng *next
|
||||
|
||||
# renaming these for clarity
|
||||
ctypedef struct LinkedGeoLoop:
|
||||
LinkedLatLng *data 'first'
|
||||
LinkedLatLng *_data_last 'last' # not needed in Cython bindings
|
||||
LinkedGeoLoop *next
|
||||
|
||||
ctypedef struct LinkedGeoPolygon:
|
||||
LinkedGeoLoop *data 'first'
|
||||
LinkedGeoLoop *_data_last 'last' # not needed in Cython bindings
|
||||
LinkedGeoPolygon *next
|
||||
|
||||
ctypedef struct GeoLoop:
|
||||
int numVerts
|
||||
LatLng *verts
|
||||
|
||||
ctypedef struct GeoPolygon:
|
||||
GeoLoop geoloop
|
||||
int numHoles
|
||||
GeoLoop *holes
|
||||
|
||||
int isValidCell(H3int h) nogil
|
||||
int isPentagon(H3int h) nogil
|
||||
int isResClassIII(H3int h) nogil
|
||||
int isValidDirectedEdge(H3int edge) nogil
|
||||
int isValidVertex(H3int v) nogil
|
||||
|
||||
double degsToRads(double degrees) nogil
|
||||
double radsToDegs(double radians) nogil
|
||||
|
||||
int getResolution(H3int h) nogil
|
||||
int getBaseCellNumber(H3int h) nogil
|
||||
|
||||
H3Error latLngToCell(const LatLng *g, int res, H3int *out) nogil
|
||||
H3Error cellToLatLng(H3int h, LatLng *) nogil
|
||||
H3Error gridDistance(H3int h1, H3int h2, int64_t *distance) nogil
|
||||
|
||||
H3Error cellToVertex(H3int cell, int vertexNum, H3int *out) nogil
|
||||
H3Error cellToVertexes(H3int cell, H3int *vertexes) nogil
|
||||
H3Error vertexToLatLng(H3int vertex, LatLng *coord) nogil
|
||||
|
||||
H3Error maxGridDiskSize(int k, int64_t *out) nogil # num/out/N?
|
||||
H3Error gridDisk(H3int h, int k, H3int *out) nogil
|
||||
|
||||
H3Error cellToParent( H3int h, int parentRes, H3int *parent) nogil
|
||||
H3Error cellToCenterChild(H3int h, int childRes, H3int *child) nogil
|
||||
H3Error cellToChildPos(H3int child, int parentRes, int64_t *out) nogil
|
||||
H3Error childPosToCell(int64_t childPos, H3int parent, int childRes, H3int *child) nogil
|
||||
|
||||
H3Error cellToChildrenSize(H3int h, int childRes, int64_t *num) nogil # num/out/N?
|
||||
H3Error cellToChildren( H3int h, int childRes, H3int *children) nogil
|
||||
|
||||
H3Error compactCells(
|
||||
const H3int *cells_u,
|
||||
H3int *cells_c,
|
||||
const int num_u
|
||||
) nogil
|
||||
H3Error uncompactCellsSize(
|
||||
const H3int *cells_c,
|
||||
const int64_t num_c,
|
||||
const int res,
|
||||
int64_t *num_u
|
||||
) nogil
|
||||
H3Error uncompactCells(
|
||||
const H3int *cells_c,
|
||||
const int num_c,
|
||||
H3int *cells_u,
|
||||
const int num_u,
|
||||
const int res
|
||||
) nogil
|
||||
|
||||
H3Error getNumCells(int res, int64_t *out) nogil
|
||||
int pentagonCount() nogil
|
||||
int res0CellCount() nogil
|
||||
H3Error getPentagons(int res, H3int *out) nogil
|
||||
H3Error getRes0Cells(H3int *out) nogil
|
||||
|
||||
H3Error gridPathCellsSize(H3int start, H3int end, int64_t *size) nogil
|
||||
H3Error gridPathCells(H3int start, H3int end, H3int *out) nogil
|
||||
|
||||
H3Error getHexagonAreaAvgKm2(int res, double *out) nogil
|
||||
H3Error getHexagonAreaAvgM2(int res, double *out) nogil
|
||||
|
||||
H3Error cellAreaRads2(H3int h, double *out) nogil
|
||||
H3Error cellAreaKm2(H3int h, double *out) nogil
|
||||
H3Error cellAreaM2(H3int h, double *out) nogil
|
||||
|
||||
H3Error maxFaceCount(H3int h, int *out) nogil
|
||||
H3Error getIcosahedronFaces(H3int h3, int *out) nogil
|
||||
|
||||
H3Error cellToLocalIj(H3int origin, H3int h3, uint32_t mode, CoordIJ *out) nogil
|
||||
H3Error localIjToCell(H3int origin, const CoordIJ *ij, uint32_t mode, H3int *out) nogil
|
||||
|
||||
H3Error gridDiskDistances(H3int origin, int k, H3int *out, int *distances) nogil
|
||||
H3Error gridRing(H3int origin, int k, H3int *out) nogil
|
||||
H3Error gridRingUnsafe(H3int origin, int k, H3int *out) nogil
|
||||
|
||||
H3Error areNeighborCells(H3int origin, H3int destination, int *out) nogil
|
||||
H3Error cellsToDirectedEdge(H3int origin, H3int destination, H3int *out) nogil
|
||||
H3Error getDirectedEdgeOrigin(H3int edge, H3int *out) nogil
|
||||
H3Error getDirectedEdgeDestination(H3int edge, H3int *out) nogil
|
||||
H3Error originToDirectedEdges(H3int origin, H3int *edges) nogil
|
||||
# todo: directedEdgeToCells
|
||||
|
||||
H3Error getHexagonEdgeLengthAvgKm(int res, double *out) nogil
|
||||
H3Error getHexagonEdgeLengthAvgM(int res, double *out) nogil
|
||||
|
||||
H3Error edgeLengthRads(H3int edge, double *out) nogil
|
||||
H3Error edgeLengthKm(H3int edge, double *out) nogil
|
||||
H3Error edgeLengthM(H3int edge, double *out) nogil
|
||||
|
||||
H3Error cellToBoundary(H3int h3, CellBoundary *gp) nogil
|
||||
H3Error directedEdgeToBoundary(H3int edge, CellBoundary *gb) nogil
|
||||
|
||||
double greatCircleDistanceRads(const LatLng *a, const LatLng *b) nogil
|
||||
double greatCircleDistanceKm(const LatLng *a, const LatLng *b) nogil
|
||||
double greatCircleDistanceM(const LatLng *a, const LatLng *b) nogil
|
||||
|
||||
H3Error cellsToLinkedMultiPolygon(const H3int *h3Set, const int numCells, LinkedGeoPolygon *out)
|
||||
void destroyLinkedMultiPolygon(LinkedGeoPolygon *polygon)
|
||||
|
||||
H3Error maxPolygonToCellsSize(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t *count)
|
||||
H3Error polygonToCells(const GeoPolygon *geoPolygon, int res, uint32_t flags, H3int *out)
|
||||
|
||||
H3Error maxPolygonToCellsSizeExperimental(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t *count)
|
||||
H3Error polygonToCellsExperimental(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t sz, H3int *out)
|
||||
|
||||
# ctypedef struct GeoMultiPolygon:
|
||||
# int numPolygons
|
||||
# GeoPolygon *polygons
|
||||
|
||||
# int hexRange(H3int origin, int k, H3int *out)
|
||||
|
||||
# int hexRangeDistances(H3int origin, int k, H3int *out, int *distances)
|
||||
|
||||
# int hexRanges(H3int *h3Set, int length, int k, H3int *out)
|
||||
|
||||
# void h3SetToLinkedGeo(const H3int *h3Set, const int numCells, LinkedGeoPolygon *out)
|
||||
|
||||
# void destroyLinkedPolygon(LinkedGeoPolygon *polygon)
|
||||
|
||||
# H3int stringToH3(const char *str)
|
||||
|
||||
# void h3ToString(H3int h, char *str, size_t sz)
|
||||
|
||||
# void getH3intesFromUnidirectionalEdge(H3int edge, H3int *originDestination)
|
||||
Binary file not shown.
-7
@@ -1,7 +0,0 @@
|
||||
from .h3lib cimport H3int
|
||||
|
||||
cpdef H3int latlng_to_cell(double lat, double lng, int res) except 1
|
||||
cpdef (double, double) cell_to_latlng(H3int h) except *
|
||||
cpdef double great_circle_distance(
|
||||
double lat1, double lng1,
|
||||
double lat2, double lng2, unit=*) except -1
|
||||
-325
@@ -1,325 +0,0 @@
|
||||
from libc.stdint cimport uint64_t
|
||||
|
||||
cimport h3lib
|
||||
from h3lib cimport bool, H3int
|
||||
|
||||
from .util cimport (
|
||||
check_cell,
|
||||
check_edge,
|
||||
check_res,
|
||||
deg2coord,
|
||||
coord2deg
|
||||
)
|
||||
|
||||
from .error_system cimport check_for_error
|
||||
|
||||
from .memory cimport H3MemoryManager
|
||||
|
||||
# TODO: We might be OK with taking the GIL for the functions in this module
|
||||
from libc.stdlib cimport (
|
||||
# malloc as h3_malloc, # not used
|
||||
calloc as h3_calloc,
|
||||
realloc as h3_realloc,
|
||||
free as h3_free,
|
||||
)
|
||||
|
||||
|
||||
cpdef H3int latlng_to_cell(double lat, double lng, int res) except 1:
|
||||
cdef:
|
||||
h3lib.LatLng c
|
||||
H3int out
|
||||
|
||||
c = deg2coord(lat, lng)
|
||||
|
||||
check_for_error(
|
||||
h3lib.latLngToCell(&c, res, &out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
cpdef (double, double) cell_to_latlng(H3int h) except *:
|
||||
"""Map an H3 cell into its centroid geo-coordinate (lat/lng)"""
|
||||
cdef:
|
||||
h3lib.LatLng c
|
||||
|
||||
check_cell(h)
|
||||
# todo: think about: if you give this an invalid cell, should it still return a lat/lng?
|
||||
# idea: safe and unsafe APIs?
|
||||
|
||||
check_for_error(
|
||||
h3lib.cellToLatLng(h, &c)
|
||||
)
|
||||
|
||||
return coord2deg(c)
|
||||
|
||||
|
||||
cdef h3lib.GeoLoop make_geoloop(latlngs) except *:
|
||||
"""
|
||||
The returned `GeoLoop` must be freed with a call to `free_geoloop`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
latlngs : list or tuple
|
||||
GeoLoop: A sequence of >= 3 (lat, lng) pairs where the last
|
||||
element may or may not be same as the first (to form a closed loop).
|
||||
The order of the pairs may be either clockwise or counterclockwise.
|
||||
"""
|
||||
cdef:
|
||||
h3lib.GeoLoop gl
|
||||
|
||||
gl.numVerts = len(latlngs)
|
||||
|
||||
# todo: need for memory management
|
||||
# can automatically free?
|
||||
gl.verts = <h3lib.LatLng*> h3_calloc(gl.numVerts, sizeof(h3lib.LatLng))
|
||||
|
||||
for i, (lat, lng) in enumerate(latlngs):
|
||||
gl.verts[i] = deg2coord(lat, lng)
|
||||
|
||||
return gl
|
||||
|
||||
|
||||
cdef free_geoloop(h3lib.GeoLoop* gl):
|
||||
h3_free(gl.verts)
|
||||
gl.verts = NULL
|
||||
|
||||
|
||||
cdef class GeoPolygon:
|
||||
cdef:
|
||||
h3lib.GeoPolygon gp
|
||||
|
||||
def __cinit__(self, outer, holes=None):
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
outer : list or tuple
|
||||
GeoLoop
|
||||
A GeoLoop is a sequence of >= 3 (lat, lng) pairs where the last
|
||||
element may or may not be same as the first (to form a closed loop).
|
||||
The order of the pairs may be either clockwise or counterclockwise.
|
||||
holes : list or tuple
|
||||
A sequence of GeoLoops
|
||||
"""
|
||||
if holes is None:
|
||||
holes = []
|
||||
|
||||
self.gp.geoloop = make_geoloop(outer)
|
||||
self.gp.numHoles = len(holes)
|
||||
self.gp.holes = NULL
|
||||
|
||||
if len(holes) > 0:
|
||||
self.gp.holes = <h3lib.GeoLoop*> h3_calloc(len(holes), sizeof(h3lib.GeoLoop))
|
||||
for i, hole in enumerate(holes):
|
||||
self.gp.holes[i] = make_geoloop(hole)
|
||||
|
||||
|
||||
def __dealloc__(self):
|
||||
free_geoloop(&self.gp.geoloop)
|
||||
|
||||
for i in range(self.gp.numHoles):
|
||||
free_geoloop(&self.gp.holes[i])
|
||||
|
||||
h3_free(self.gp.holes)
|
||||
|
||||
|
||||
def polygon_to_cells(outer, int res, holes=None):
|
||||
""" Get the set of cells whose center is contained in a polygon.
|
||||
|
||||
The polygon is defined similarity to the GeoJson standard, with an exterior
|
||||
`outer` ring of lat/lng points, and a list of `holes`, each of which are also
|
||||
rings of lat/lng points.
|
||||
|
||||
Each ring may be in clockwise or counter-clockwise order
|
||||
(right-hand rule or not), and may or may not be a closed loop (where the last
|
||||
element is equal to the first).
|
||||
The GeoJSON spec requires the right-hand rule and a closed loop, but
|
||||
this function relaxes those constraints.
|
||||
|
||||
Unlike the GeoJson standard, the elements of the lat/lng pairs of each
|
||||
ring are in lat/lng order, instead of lng/lat order.
|
||||
|
||||
We'll handle translation to different formats in the Python code,
|
||||
rather than the Cython code.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
outer : list or tuple
|
||||
A ring given by a sequence of lat/lng pairs.
|
||||
res : int
|
||||
The resolution of the output hexagons
|
||||
holes : list or tuple
|
||||
A collection of rings, each given by a sequence of lat/lng pairs.
|
||||
These describe any the "holes" in the polygon.
|
||||
"""
|
||||
cdef:
|
||||
uint64_t n
|
||||
|
||||
check_res(res)
|
||||
|
||||
if not outer:
|
||||
return H3MemoryManager(0).to_mv()
|
||||
|
||||
gp = GeoPolygon(outer, holes=holes)
|
||||
|
||||
check_for_error(
|
||||
h3lib.maxPolygonToCellsSize(&gp.gp, res, 0, &n)
|
||||
)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.polygonToCells(&gp.gp, res, 0, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
def polygons_to_cells(polygons, int res):
|
||||
mvs = [
|
||||
polygon_to_cells(outer=poly.outer, res=res, holes=poly.holes)
|
||||
for poly in polygons
|
||||
]
|
||||
|
||||
n = sum(map(len, mvs))
|
||||
hmm = H3MemoryManager(n)
|
||||
|
||||
# probably super inefficient, but it is working!
|
||||
# tood: move this to C
|
||||
k = 0
|
||||
for mv in mvs:
|
||||
for v in mv:
|
||||
hmm.ptr[k] = v
|
||||
k += 1
|
||||
|
||||
return hmm.to_mv()
|
||||
|
||||
|
||||
def polygon_to_cells_experimental(outer, int res, int flag, holes=None):
|
||||
""" Get the set of cells whose center is contained in a polygon.
|
||||
|
||||
The polygon is defined similarity to the GeoJson standard, with an exterior
|
||||
`outer` ring of lat/lng points, and a list of `holes`, each of which are also
|
||||
rings of lat/lng points.
|
||||
|
||||
Each ring may be in clockwise or counter-clockwise order
|
||||
(right-hand rule or not), and may or may not be a closed loop (where the last
|
||||
element is equal to the first).
|
||||
The GeoJSON spec requires the right-hand rule and a closed loop, but
|
||||
this function relaxes those constraints.
|
||||
|
||||
Unlike the GeoJson standard, the elements of the lat/lng pairs of each
|
||||
ring are in lat/lng order, instead of lng/lat order.
|
||||
|
||||
We'll handle translation to different formats in the Python code,
|
||||
rather than the Cython code.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
outer : list or tuple
|
||||
A ring given by a sequence of lat/lng pairs.
|
||||
res : int
|
||||
The resolution of the output hexagons
|
||||
flag : int
|
||||
Polygon to cells flag, such as containment mode.
|
||||
holes : list or tuple
|
||||
A collection of rings, each given by a sequence of lat/lng pairs.
|
||||
These describe any the "holes" in the polygon.
|
||||
"""
|
||||
cdef:
|
||||
uint64_t n
|
||||
|
||||
check_res(res)
|
||||
|
||||
if not outer:
|
||||
return H3MemoryManager(0).to_mv()
|
||||
|
||||
gp = GeoPolygon(outer, holes=holes)
|
||||
|
||||
check_for_error(
|
||||
h3lib.maxPolygonToCellsSizeExperimental(&gp.gp, res, flag, &n)
|
||||
)
|
||||
|
||||
hmm = H3MemoryManager(n)
|
||||
check_for_error(
|
||||
h3lib.polygonToCellsExperimental(&gp.gp, res, flag, n, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
def polygons_to_cells_experimental(polygons, int res, int flag):
|
||||
mvs = [
|
||||
polygon_to_cells_experimental(outer=poly.outer, res=res, holes=poly.holes, flag=flag)
|
||||
for poly in polygons
|
||||
]
|
||||
|
||||
n = sum(map(len, mvs))
|
||||
hmm = H3MemoryManager(n)
|
||||
|
||||
# probably super inefficient, but it is working!
|
||||
# tood: move this to C
|
||||
k = 0
|
||||
for mv in mvs:
|
||||
for v in mv:
|
||||
hmm.ptr[k] = v
|
||||
k += 1
|
||||
|
||||
return hmm.to_mv()
|
||||
|
||||
|
||||
def cell_to_boundary(H3int h):
|
||||
"""Compose an array of geo-coordinates that outlines a hexagonal cell"""
|
||||
cdef:
|
||||
h3lib.CellBoundary gb
|
||||
|
||||
check_cell(h)
|
||||
|
||||
h3lib.cellToBoundary(h, &gb)
|
||||
|
||||
verts = tuple(
|
||||
coord2deg(gb.verts[i])
|
||||
for i in range(gb.num_verts)
|
||||
)
|
||||
|
||||
return verts
|
||||
|
||||
|
||||
def directed_edge_to_boundary(H3int edge):
|
||||
""" Returns the CellBoundary containing the coordinates of the edge
|
||||
"""
|
||||
cdef:
|
||||
h3lib.CellBoundary gb
|
||||
|
||||
check_edge(edge)
|
||||
|
||||
h3lib.directedEdgeToBoundary(edge, &gb)
|
||||
|
||||
# todo: move this verts transform into the CellBoundary object
|
||||
verts = tuple(
|
||||
coord2deg(gb.verts[i])
|
||||
for i in range(gb.num_verts)
|
||||
)
|
||||
|
||||
return verts
|
||||
|
||||
|
||||
cpdef double great_circle_distance(
|
||||
double lat1, double lng1,
|
||||
double lat2, double lng2, unit='km') except -1:
|
||||
|
||||
a = deg2coord(lat1, lng1)
|
||||
b = deg2coord(lat2, lng2)
|
||||
|
||||
if unit == 'rads':
|
||||
d = h3lib.greatCircleDistanceRads(&a, &b)
|
||||
elif unit == 'km':
|
||||
d = h3lib.greatCircleDistanceKm(&a, &b)
|
||||
elif unit == 'm':
|
||||
d = h3lib.greatCircleDistanceM(&a, &b)
|
||||
else:
|
||||
raise ValueError('Unknown unit: {}'.format(unit))
|
||||
|
||||
return d
|
||||
Binary file not shown.
-12
@@ -1,12 +0,0 @@
|
||||
from .h3lib cimport H3int
|
||||
|
||||
cdef class H3MemoryManager:
|
||||
cdef:
|
||||
size_t n
|
||||
H3int* ptr
|
||||
|
||||
cdef H3int[:] to_mv(self)
|
||||
cdef H3int[:] to_mv_keep_zeros(self)
|
||||
|
||||
cdef int[:] int_mv(size_t n)
|
||||
cpdef H3int[:] iter_to_mv(cells)
|
||||
-248
@@ -1,248 +0,0 @@
|
||||
from cython.view cimport array
|
||||
from .h3lib cimport H3int
|
||||
|
||||
"""
|
||||
### Memory allocation options
|
||||
|
||||
We have a few options for the memory allocation functions.
|
||||
There's a trade-off between using the Python allocators which let Python
|
||||
track memory usage and offers some optimizations vs the system
|
||||
allocators, which do not need to acquire the GIL.
|
||||
"""
|
||||
|
||||
"""
|
||||
System allocation functions. These do not acquire the GIL.
|
||||
"""
|
||||
from libc.stdlib cimport (
|
||||
# malloc as h3_malloc, # not used
|
||||
calloc as h3_calloc,
|
||||
realloc as h3_realloc,
|
||||
free as h3_free,
|
||||
)
|
||||
|
||||
|
||||
"""
|
||||
PyMem_Raw* functions should just be wrappers around system allocators
|
||||
also given in libc.stdlib. These functions do not acquire the GIL.
|
||||
|
||||
Note that these do not have a calloc function until py 3.5 and Cython 3.0,
|
||||
so we would need to zero-out memory manually.
|
||||
|
||||
https://python.readthedocs.io/en/stable/c-api/memory.html#raw-memory-interface
|
||||
"""
|
||||
# from cpython.mem cimport (
|
||||
# PyMem_RawMalloc as h3_malloc,
|
||||
# # PyMem_RawCalloc as h3_calloc, # only in Python >=3.5 (and Cython >=3.0?)
|
||||
# PyMem_RawRealloc as h3_realloc,
|
||||
# PyMem_RawFree as h3_free,
|
||||
# )
|
||||
|
||||
|
||||
"""
|
||||
These functions use the Python allocator (instead of the system allocator),
|
||||
which offers some optimizations for Python, and allows Python to track
|
||||
memory usage. However, these functions must acquire the GIL.
|
||||
|
||||
Note that these do not have a calloc function until py 3.5 and Cython 3.0,
|
||||
so we would need to zero-out memory manually.
|
||||
|
||||
https://cython.readthedocs.io/en/stable/src/tutorial/memory_allocation.html
|
||||
https://python.readthedocs.io/en/stable/c-api/memory.html#memory-interface
|
||||
"""
|
||||
# from cpython.mem cimport (
|
||||
# PyMem_Malloc as h3_malloc,
|
||||
# # PyMem_Calloc as h3_calloc, # only in Python >=3.5 (and Cython >=3.0?)
|
||||
# PyMem_Realloc as h3_realloc,
|
||||
# PyMem_Free as h3_free,
|
||||
# )
|
||||
|
||||
|
||||
cdef size_t move_nonzeros(H3int* a, size_t n):
|
||||
""" Move nonzero elements to front of array `a` of length `n`.
|
||||
Return the number of nonzero elements.
|
||||
|
||||
Loop invariant: Everything *before* `i` or *after* `j` is "done".
|
||||
Move `i` and `j` inwards until they equal, and exit.
|
||||
You can move `i` forward until there's a zero in front of it.
|
||||
You can move `j` backward until there's a nonzero to the left of it.
|
||||
Anything to the right of `j` is "junk" that can be reallocated.
|
||||
|
||||
| a | b | 0 | c | d | ... |
|
||||
^ ^
|
||||
i j
|
||||
|
||||
|
||||
| a | b | d | c | d | ... |
|
||||
^ ^
|
||||
i j
|
||||
"""
|
||||
cdef:
|
||||
size_t i = 0
|
||||
size_t j = n
|
||||
|
||||
while i < j:
|
||||
if a[j-1] == 0:
|
||||
j -= 1
|
||||
continue
|
||||
|
||||
if a[i] != 0:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# if we're here, we know:
|
||||
# a[i] == 0
|
||||
# a[j-1] != 0
|
||||
# i < j
|
||||
# so we can swap! (actually, move a[j-1] -> a[i])
|
||||
a[i] = a[j-1]
|
||||
j -= 1
|
||||
|
||||
return i
|
||||
|
||||
|
||||
cdef H3int[:] empty_memory_view():
|
||||
# todo: get rid of this?
|
||||
# there's gotta be a better way to do this...
|
||||
# create an empty cython.view.array?
|
||||
cdef:
|
||||
H3int a[1]
|
||||
|
||||
return (<H3int[:]>a)[:0]
|
||||
|
||||
|
||||
cdef _remove_zeros(H3MemoryManager x):
|
||||
x.n = move_nonzeros(x.ptr, x.n)
|
||||
|
||||
if x.n == 0:
|
||||
h3_free(x.ptr)
|
||||
x.ptr = NULL
|
||||
else:
|
||||
x.ptr = <H3int*> h3_realloc(x.ptr, x.n*sizeof(H3int))
|
||||
if not x.ptr:
|
||||
raise MemoryError()
|
||||
|
||||
|
||||
cdef H3int[:] _copy_to_mv(const H3int* ptr, size_t n):
|
||||
cdef:
|
||||
array arr
|
||||
|
||||
arr = <H3int[:n]> ptr
|
||||
arr.callback_free_data = h3_free
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
cdef H3int[:] _create_mv(H3MemoryManager x):
|
||||
if x.n == 0:
|
||||
h3_free(x.ptr)
|
||||
x.ptr = NULL
|
||||
mv = empty_memory_view()
|
||||
else:
|
||||
mv = _copy_to_mv(x.ptr, x.n)
|
||||
|
||||
# responsibility for the memory moves from this object to the array/memoryview
|
||||
x.ptr = NULL
|
||||
x.n = 0
|
||||
|
||||
return mv
|
||||
|
||||
|
||||
"""
|
||||
TODO: The not None declaration for the argument automatically rejects None values as input, which would otherwise be allowed. The reason why None is allowed by default is that it is conveniently used for return arguments:
|
||||
https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#syntax
|
||||
|
||||
TODO: potential optimization: https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#performance-disabling-initialization-checks
|
||||
|
||||
## future improvements:
|
||||
|
||||
- abolish any appearance of &thing[0]. (i.e., identical interfaces)
|
||||
- can i make the interface for all these memory views identical?
|
||||
"""
|
||||
|
||||
cdef class H3MemoryManager:
|
||||
"""
|
||||
Cython object in charge of allocating and freeing memory for arrays
|
||||
of H3 indexes.
|
||||
|
||||
Initially allocates memory and provides access through `self.ptr` and
|
||||
`self.n`.
|
||||
|
||||
The `to_mv()` function removes responsibility for the allocated memory
|
||||
from this object to a memory view object. A memory view object automatically
|
||||
deallocates its memory during garbage collection.
|
||||
|
||||
If the H3MemoryManager is garbage collected before running `to_mv()`,
|
||||
it will deallocate its memory itself.
|
||||
|
||||
This pattern is useful for a few reasons:
|
||||
|
||||
- provide convenient access to the raw memory pointer and length for passing
|
||||
to h3lib functions
|
||||
- remove zeroes from the array output (some h3lib functions may return
|
||||
results with zeros/H3NULL values)
|
||||
- cython and python array types have weird interfaces; memoryviews are
|
||||
much cleaner
|
||||
|
||||
If we find a better way to do these then this class may no longer be
|
||||
necessary.
|
||||
|
||||
TODO: consider a context manager pattern
|
||||
"""
|
||||
def __cinit__(self, size_t n):
|
||||
self.n = n
|
||||
self.ptr = <H3int*> h3_calloc(self.n, sizeof(H3int))
|
||||
|
||||
if not self.ptr:
|
||||
raise MemoryError()
|
||||
|
||||
cdef H3int[:] to_mv_keep_zeros(self):
|
||||
# todo: this could be a private method
|
||||
return _create_mv(self)
|
||||
|
||||
cdef H3int[:] to_mv(self):
|
||||
_remove_zeros(self)
|
||||
return _create_mv(self)
|
||||
|
||||
def __dealloc__(self):
|
||||
# If the memory has been handed off to a memoryview, this pointer
|
||||
# should be NULL, and deallocing on NULL is fine.
|
||||
# If the pointer is *not* NULL, then this means the MemoryManager
|
||||
# has is still responsible for the memory (it hasn't given the memory away to another object).
|
||||
h3_free(self.ptr)
|
||||
|
||||
|
||||
"""
|
||||
todo: combine with the H3MemoryManager using fused types?
|
||||
https://cython.readthedocs.io/en/stable/src/userguide/fusedtypes.html
|
||||
"""
|
||||
cdef int[:] int_mv(size_t n):
|
||||
cdef:
|
||||
array arr
|
||||
|
||||
if n == 0:
|
||||
raise MemoryError()
|
||||
else:
|
||||
ptr = <int*> h3_calloc(n, sizeof(int))
|
||||
if ptr is NULL:
|
||||
raise MemoryError()
|
||||
|
||||
arr = <int[:n]> ptr
|
||||
arr.callback_free_data = h3_free
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
cpdef H3int[:] iter_to_mv(cells):
|
||||
""" cells needs to be an iterable that knows its size...
|
||||
or should we have it match the np.fromiter function, which infers if not available?
|
||||
"""
|
||||
cdef:
|
||||
H3int[:] mv
|
||||
|
||||
n = len(cells)
|
||||
mv = H3MemoryManager(n).to_mv_keep_zeros()
|
||||
|
||||
for i,h in enumerate(cells):
|
||||
mv[i] = h
|
||||
|
||||
return mv
|
||||
Binary file not shown.
-60
@@ -1,60 +0,0 @@
|
||||
cimport h3lib
|
||||
from h3lib cimport H3int
|
||||
from .util cimport check_cell, coord2deg
|
||||
|
||||
|
||||
# todo: it's driving me crazy that these three functions are all essentially the same linked list walker...
|
||||
# grumble: no way to do iterators in with cdef functions!
|
||||
cdef walk_polys(const h3lib.LinkedGeoPolygon* L):
|
||||
out = []
|
||||
while L:
|
||||
out += [walk_loops(L.data)]
|
||||
L = L.next
|
||||
|
||||
return out
|
||||
|
||||
|
||||
cdef walk_loops(const h3lib.LinkedGeoLoop* L):
|
||||
out = []
|
||||
while L:
|
||||
out += [walk_coords(L.data)]
|
||||
L = L.next
|
||||
|
||||
return out
|
||||
|
||||
|
||||
cdef walk_coords(const h3lib.LinkedLatLng* L):
|
||||
out = []
|
||||
while L:
|
||||
out += [coord2deg(L.data)]
|
||||
L = L.next
|
||||
|
||||
return out
|
||||
|
||||
# todo: tuples instead of lists?
|
||||
def _to_multi_polygon(const H3int[:] cells):
|
||||
cdef:
|
||||
h3lib.LinkedGeoPolygon polygon
|
||||
|
||||
for h in cells:
|
||||
check_cell(h)
|
||||
|
||||
h3lib.cellsToLinkedMultiPolygon(&cells[0], len(cells), &polygon)
|
||||
|
||||
out = walk_polys(&polygon)
|
||||
|
||||
# we're still responsible for cleaning up the passed in `polygon`,
|
||||
# but not a problem here, since it is stack allocated
|
||||
h3lib.destroyLinkedMultiPolygon(&polygon)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def cells_to_multi_polygon(const H3int[:] cells):
|
||||
# todo: gotta be a more elegant way to handle these...
|
||||
if len(cells) == 0:
|
||||
return []
|
||||
|
||||
multipoly = _to_multi_polygon(cells)
|
||||
|
||||
return multipoly
|
||||
Binary file not shown.
-13
@@ -1,13 +0,0 @@
|
||||
from .h3lib cimport H3int, H3str, LatLng
|
||||
|
||||
cdef LatLng deg2coord(double lat, double lng) nogil
|
||||
cdef (double, double) coord2deg(LatLng c) nogil
|
||||
|
||||
cpdef H3int str_to_int(H3str h) except? 0
|
||||
cpdef H3str int_to_str(H3int x)
|
||||
|
||||
cdef check_cell(H3int h)
|
||||
cdef check_edge(H3int e)
|
||||
cdef check_res(int res)
|
||||
cdef check_vertex(H3int v)
|
||||
cdef check_distance(int k)
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
from .h3lib cimport H3int, H3str, isValidCell, isValidDirectedEdge, isValidVertex
|
||||
|
||||
cimport h3lib
|
||||
|
||||
from .error_system import (
|
||||
H3ResDomainError,
|
||||
H3DomainError,
|
||||
H3DirEdgeInvalidError,
|
||||
H3CellInvalidError,
|
||||
H3VertexInvalidError
|
||||
)
|
||||
|
||||
cdef h3lib.LatLng deg2coord(double lat, double lng) nogil:
|
||||
cdef:
|
||||
h3lib.LatLng c
|
||||
|
||||
c.lat = h3lib.degsToRads(lat)
|
||||
c.lng = h3lib.degsToRads(lng)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
cdef (double, double) coord2deg(h3lib.LatLng c) nogil:
|
||||
return (
|
||||
h3lib.radsToDegs(c.lat),
|
||||
h3lib.radsToDegs(c.lng)
|
||||
)
|
||||
|
||||
|
||||
cpdef basestring c_version():
|
||||
v = (
|
||||
h3lib.H3_VERSION_MAJOR,
|
||||
h3lib.H3_VERSION_MINOR,
|
||||
h3lib.H3_VERSION_PATCH,
|
||||
)
|
||||
|
||||
return '{}.{}.{}'.format(*v)
|
||||
|
||||
|
||||
cpdef H3int str_to_int(H3str h) except? 0:
|
||||
return int(h, 16)
|
||||
|
||||
|
||||
cpdef H3str int_to_str(H3int x):
|
||||
""" Convert H3 integer to hex string representation
|
||||
|
||||
Need to be careful in Python 2 because `hex(x)` may return a string
|
||||
with a trailing `L` character (denoting a "large" integer).
|
||||
The formatting approach below avoids this.
|
||||
|
||||
Also need to be careful about unicode/str differences.
|
||||
"""
|
||||
return '{:x}'.format(x)
|
||||
|
||||
|
||||
cdef check_cell(H3int h):
|
||||
""" Check if valid H3 "cell" (hexagon or pentagon).
|
||||
|
||||
Does not check if a valid H3 edge, for example.
|
||||
|
||||
Since this function is used by multiple interfaces (int or str),
|
||||
we want the error message to be informative to the user
|
||||
in either case.
|
||||
|
||||
We use the builtin `hex` function instead of `int_to_str` to
|
||||
prepend `0x` to indicate that this **integer** representation
|
||||
is incorrect, but in a format that is easily compared to
|
||||
`str` inputs.
|
||||
"""
|
||||
if isValidCell(h) == 0:
|
||||
raise H3CellInvalidError('Integer is not a valid H3 cell: {}'.format(hex(h)))
|
||||
|
||||
cdef check_edge(H3int e):
|
||||
if isValidDirectedEdge(e) == 0:
|
||||
raise H3DirEdgeInvalidError('Integer is not a valid H3 edge: {}'.format(hex(e)))
|
||||
|
||||
cdef check_vertex(H3int v):
|
||||
if isValidVertex(v) == 0:
|
||||
raise H3VertexInvalidError('Integer is not a valid H3 vertex: {}'.format(hex(v)))
|
||||
|
||||
cdef check_res(int res):
|
||||
if (res < 0) or (res > 15):
|
||||
raise H3ResDomainError(res)
|
||||
|
||||
cdef check_distance(int k):
|
||||
if k < 0:
|
||||
raise H3DomainError(
|
||||
'Grid distances must be nonnegative. Received: {}'.format(k)
|
||||
)
|
||||
Binary file not shown.
-6
@@ -1,6 +0,0 @@
|
||||
from .h3lib cimport bool, H3int
|
||||
|
||||
cpdef H3int cell_to_vertex(H3int h, int vertex_num) except 1
|
||||
cpdef H3int[:] cell_to_vertexes(H3int h)
|
||||
cpdef (double, double) vertex_to_latlng(H3int v) except *
|
||||
cpdef bool is_valid_vertex(H3int v)
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
cimport h3lib
|
||||
from h3lib cimport bool, H3int
|
||||
|
||||
from .util cimport (
|
||||
check_cell,
|
||||
check_vertex,
|
||||
coord2deg
|
||||
)
|
||||
|
||||
from .error_system cimport check_for_error
|
||||
|
||||
from .memory cimport H3MemoryManager
|
||||
|
||||
|
||||
cpdef H3int cell_to_vertex(H3int h, int vertex_num) except 1:
|
||||
cdef:
|
||||
H3int out
|
||||
|
||||
check_cell(h)
|
||||
|
||||
check_for_error(
|
||||
h3lib.cellToVertex(h, vertex_num, &out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
cpdef H3int[:] cell_to_vertexes(H3int h):
|
||||
cdef:
|
||||
H3int out
|
||||
|
||||
check_cell(h)
|
||||
|
||||
hmm = H3MemoryManager(6)
|
||||
check_for_error(
|
||||
h3lib.cellToVertexes(h, hmm.ptr)
|
||||
)
|
||||
mv = hmm.to_mv()
|
||||
|
||||
return mv
|
||||
|
||||
cpdef (double, double) vertex_to_latlng(H3int v) except *:
|
||||
cdef:
|
||||
h3lib.LatLng c
|
||||
|
||||
check_vertex(v)
|
||||
|
||||
check_for_error(
|
||||
h3lib.vertexToLatLng(v, &c)
|
||||
)
|
||||
|
||||
return coord2deg(c)
|
||||
|
||||
cpdef bool is_valid_vertex(H3int v):
|
||||
return h3lib.isValidVertex(v) == 1
|
||||
Vendored
+1
-3
@@ -1,3 +1 @@
|
||||
from importlib import metadata
|
||||
|
||||
__version__ = metadata.version(__package__ or __name__)
|
||||
__version__ = "4.4.2"
|
||||
|
||||
+129
@@ -1,5 +1,6 @@
|
||||
# This file is **symlinked** across the APIs to ensure they are exactly the same.
|
||||
from typing import Literal
|
||||
from array import array
|
||||
|
||||
from ... import _cy
|
||||
from ..._h3shape import (
|
||||
@@ -128,6 +129,20 @@ def average_hexagon_edge_length(res, unit='km'):
|
||||
return _cy.average_hexagon_edge_length(res, unit)
|
||||
|
||||
|
||||
def is_valid_index(h):
|
||||
"""Validates *any* H3 index (cell, vertex, or directed edge).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
"""
|
||||
try:
|
||||
h = _in_scalar(h)
|
||||
return _cy.is_valid_index(h)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_cell(h):
|
||||
"""
|
||||
Validates an H3 cell (hexagon or pentagon).
|
||||
@@ -736,10 +751,124 @@ def get_base_cell_number(h):
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(57, 2, 1, 4)
|
||||
>>> h
|
||||
'83728cfffffffff'
|
||||
>> get_base_cell_number(h)
|
||||
57
|
||||
"""
|
||||
return _cy.get_base_cell_number(_in_scalar(h))
|
||||
|
||||
|
||||
def get_index_digit(h, res):
|
||||
"""
|
||||
Get the index digit of a cell at the given resolution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell whose index digit will be returned.
|
||||
res : int
|
||||
Resolution (``>= 1``) at which to read the digit.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The index digit at the requested resolution.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4)
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> get_index_digit(h, 1)
|
||||
2
|
||||
>>> get_index_digit(h, 2)
|
||||
1
|
||||
>>> get_index_digit(h, 3)
|
||||
4
|
||||
"""
|
||||
return _cy.get_index_digit(_in_scalar(h), res)
|
||||
|
||||
|
||||
def construct_cell(base_cell_number, *digits, res=None):
|
||||
"""
|
||||
Construct cell from base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_cell_number : int
|
||||
Base cell *number* (``0`` to ``121``).
|
||||
*digits : int
|
||||
Sequence of index digits (``0`` to ``6``).
|
||||
Length of digits will be the resulting resolution of the output cell.
|
||||
res : int, optional
|
||||
Resolution of the constructed cell. If provided, it must equal
|
||||
``len(digits)``; otherwise it is inferred from the number of digits.
|
||||
|
||||
Returns
|
||||
-------
|
||||
H3Cell
|
||||
The constructed cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
'830e8cfffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
'841e057ffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3, res=4)
|
||||
'841e057ffffffff'
|
||||
"""
|
||||
if (res is not None) and (len(digits) != res):
|
||||
raise ValueError('Resolution must match number of digits.')
|
||||
|
||||
digits = array('i', digits)
|
||||
o = _cy.construct_cell(base_cell_number, digits)
|
||||
return _out_scalar(o)
|
||||
|
||||
|
||||
def deconstruct_cell(h):
|
||||
"""
|
||||
Deconstruct cell into base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell to deconstruct.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
[base_cell_number, digit1, digit2, ..., digitN]
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(7, 2, 1, 4)
|
||||
|
||||
>>> h = construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
>>> h
|
||||
'841e057ffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(15, 0, 0, 5, 3)
|
||||
>>> construct_cell(*deconstruct_cell(h), 0) == cell_to_center_child(h)
|
||||
"""
|
||||
res = get_resolution(h)
|
||||
bc = get_base_cell_number(h)
|
||||
digits = [get_index_digit(h, r + 1) for r in range(res)]
|
||||
|
||||
return [bc, *digits]
|
||||
|
||||
|
||||
def are_neighbor_cells(h1, h2):
|
||||
"""
|
||||
Returns ``True`` if ``h1`` and ``h2`` are neighboring cells.
|
||||
|
||||
+129
@@ -1,5 +1,6 @@
|
||||
# This file is **symlinked** across the APIs to ensure they are exactly the same.
|
||||
from typing import Literal
|
||||
from array import array
|
||||
|
||||
from ... import _cy
|
||||
from ..._h3shape import (
|
||||
@@ -128,6 +129,20 @@ def average_hexagon_edge_length(res, unit='km'):
|
||||
return _cy.average_hexagon_edge_length(res, unit)
|
||||
|
||||
|
||||
def is_valid_index(h):
|
||||
"""Validates *any* H3 index (cell, vertex, or directed edge).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
"""
|
||||
try:
|
||||
h = _in_scalar(h)
|
||||
return _cy.is_valid_index(h)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_cell(h):
|
||||
"""
|
||||
Validates an H3 cell (hexagon or pentagon).
|
||||
@@ -736,10 +751,124 @@ def get_base_cell_number(h):
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(57, 2, 1, 4)
|
||||
>>> h
|
||||
'83728cfffffffff'
|
||||
>> get_base_cell_number(h)
|
||||
57
|
||||
"""
|
||||
return _cy.get_base_cell_number(_in_scalar(h))
|
||||
|
||||
|
||||
def get_index_digit(h, res):
|
||||
"""
|
||||
Get the index digit of a cell at the given resolution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell whose index digit will be returned.
|
||||
res : int
|
||||
Resolution (``>= 1``) at which to read the digit.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The index digit at the requested resolution.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4)
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> get_index_digit(h, 1)
|
||||
2
|
||||
>>> get_index_digit(h, 2)
|
||||
1
|
||||
>>> get_index_digit(h, 3)
|
||||
4
|
||||
"""
|
||||
return _cy.get_index_digit(_in_scalar(h), res)
|
||||
|
||||
|
||||
def construct_cell(base_cell_number, *digits, res=None):
|
||||
"""
|
||||
Construct cell from base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_cell_number : int
|
||||
Base cell *number* (``0`` to ``121``).
|
||||
*digits : int
|
||||
Sequence of index digits (``0`` to ``6``).
|
||||
Length of digits will be the resulting resolution of the output cell.
|
||||
res : int, optional
|
||||
Resolution of the constructed cell. If provided, it must equal
|
||||
``len(digits)``; otherwise it is inferred from the number of digits.
|
||||
|
||||
Returns
|
||||
-------
|
||||
H3Cell
|
||||
The constructed cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
'830e8cfffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
'841e057ffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3, res=4)
|
||||
'841e057ffffffff'
|
||||
"""
|
||||
if (res is not None) and (len(digits) != res):
|
||||
raise ValueError('Resolution must match number of digits.')
|
||||
|
||||
digits = array('i', digits)
|
||||
o = _cy.construct_cell(base_cell_number, digits)
|
||||
return _out_scalar(o)
|
||||
|
||||
|
||||
def deconstruct_cell(h):
|
||||
"""
|
||||
Deconstruct cell into base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell to deconstruct.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
[base_cell_number, digit1, digit2, ..., digitN]
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(7, 2, 1, 4)
|
||||
|
||||
>>> h = construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
>>> h
|
||||
'841e057ffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(15, 0, 0, 5, 3)
|
||||
>>> construct_cell(*deconstruct_cell(h), 0) == cell_to_center_child(h)
|
||||
"""
|
||||
res = get_resolution(h)
|
||||
bc = get_base_cell_number(h)
|
||||
digits = [get_index_digit(h, r + 1) for r in range(res)]
|
||||
|
||||
return [bc, *digits]
|
||||
|
||||
|
||||
def are_neighbor_cells(h1, h2):
|
||||
"""
|
||||
Returns ``True`` if ``h1`` and ``h2`` are neighboring cells.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# This file is **symlinked** across the APIs to ensure they are exactly the same.
|
||||
from typing import Literal
|
||||
from array import array
|
||||
|
||||
from ... import _cy
|
||||
from ..._h3shape import (
|
||||
@@ -128,6 +129,20 @@ def average_hexagon_edge_length(res, unit='km'):
|
||||
return _cy.average_hexagon_edge_length(res, unit)
|
||||
|
||||
|
||||
def is_valid_index(h):
|
||||
"""Validates *any* H3 index (cell, vertex, or directed edge).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
"""
|
||||
try:
|
||||
h = _in_scalar(h)
|
||||
return _cy.is_valid_index(h)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_cell(h):
|
||||
"""
|
||||
Validates an H3 cell (hexagon or pentagon).
|
||||
@@ -736,10 +751,124 @@ def get_base_cell_number(h):
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(57, 2, 1, 4)
|
||||
>>> h
|
||||
'83728cfffffffff'
|
||||
>> get_base_cell_number(h)
|
||||
57
|
||||
"""
|
||||
return _cy.get_base_cell_number(_in_scalar(h))
|
||||
|
||||
|
||||
def get_index_digit(h, res):
|
||||
"""
|
||||
Get the index digit of a cell at the given resolution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell whose index digit will be returned.
|
||||
res : int
|
||||
Resolution (``>= 1``) at which to read the digit.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The index digit at the requested resolution.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4)
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> get_index_digit(h, 1)
|
||||
2
|
||||
>>> get_index_digit(h, 2)
|
||||
1
|
||||
>>> get_index_digit(h, 3)
|
||||
4
|
||||
"""
|
||||
return _cy.get_index_digit(_in_scalar(h), res)
|
||||
|
||||
|
||||
def construct_cell(base_cell_number, *digits, res=None):
|
||||
"""
|
||||
Construct cell from base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_cell_number : int
|
||||
Base cell *number* (``0`` to ``121``).
|
||||
*digits : int
|
||||
Sequence of index digits (``0`` to ``6``).
|
||||
Length of digits will be the resulting resolution of the output cell.
|
||||
res : int, optional
|
||||
Resolution of the constructed cell. If provided, it must equal
|
||||
``len(digits)``; otherwise it is inferred from the number of digits.
|
||||
|
||||
Returns
|
||||
-------
|
||||
H3Cell
|
||||
The constructed cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
'830e8cfffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
'841e057ffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3, res=4)
|
||||
'841e057ffffffff'
|
||||
"""
|
||||
if (res is not None) and (len(digits) != res):
|
||||
raise ValueError('Resolution must match number of digits.')
|
||||
|
||||
digits = array('i', digits)
|
||||
o = _cy.construct_cell(base_cell_number, digits)
|
||||
return _out_scalar(o)
|
||||
|
||||
|
||||
def deconstruct_cell(h):
|
||||
"""
|
||||
Deconstruct cell into base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell to deconstruct.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
[base_cell_number, digit1, digit2, ..., digitN]
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(7, 2, 1, 4)
|
||||
|
||||
>>> h = construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
>>> h
|
||||
'841e057ffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(15, 0, 0, 5, 3)
|
||||
>>> construct_cell(*deconstruct_cell(h), 0) == cell_to_center_child(h)
|
||||
"""
|
||||
res = get_resolution(h)
|
||||
bc = get_base_cell_number(h)
|
||||
digits = [get_index_digit(h, r + 1) for r in range(res)]
|
||||
|
||||
return [bc, *digits]
|
||||
|
||||
|
||||
def are_neighbor_cells(h1, h2):
|
||||
"""
|
||||
Returns ``True`` if ``h1`` and ``h2`` are neighboring cells.
|
||||
|
||||
+129
@@ -1,5 +1,6 @@
|
||||
# This file is **symlinked** across the APIs to ensure they are exactly the same.
|
||||
from typing import Literal
|
||||
from array import array
|
||||
|
||||
from ... import _cy
|
||||
from ..._h3shape import (
|
||||
@@ -128,6 +129,20 @@ def average_hexagon_edge_length(res, unit='km'):
|
||||
return _cy.average_hexagon_edge_length(res, unit)
|
||||
|
||||
|
||||
def is_valid_index(h):
|
||||
"""Validates *any* H3 index (cell, vertex, or directed edge).
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
"""
|
||||
try:
|
||||
h = _in_scalar(h)
|
||||
return _cy.is_valid_index(h)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
|
||||
def is_valid_cell(h):
|
||||
"""
|
||||
Validates an H3 cell (hexagon or pentagon).
|
||||
@@ -736,10 +751,124 @@ def get_base_cell_number(h):
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(57, 2, 1, 4)
|
||||
>>> h
|
||||
'83728cfffffffff'
|
||||
>> get_base_cell_number(h)
|
||||
57
|
||||
"""
|
||||
return _cy.get_base_cell_number(_in_scalar(h))
|
||||
|
||||
|
||||
def get_index_digit(h, res):
|
||||
"""
|
||||
Get the index digit of a cell at the given resolution.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell whose index digit will be returned.
|
||||
res : int
|
||||
Resolution (``>= 1``) at which to read the digit.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
The index digit at the requested resolution.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4)
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> get_index_digit(h, 1)
|
||||
2
|
||||
>>> get_index_digit(h, 2)
|
||||
1
|
||||
>>> get_index_digit(h, 3)
|
||||
4
|
||||
"""
|
||||
return _cy.get_index_digit(_in_scalar(h), res)
|
||||
|
||||
|
||||
def construct_cell(base_cell_number, *digits, res=None):
|
||||
"""
|
||||
Construct cell from base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
base_cell_number : int
|
||||
Base cell *number* (``0`` to ``121``).
|
||||
*digits : int
|
||||
Sequence of index digits (``0`` to ``6``).
|
||||
Length of digits will be the resulting resolution of the output cell.
|
||||
res : int, optional
|
||||
Resolution of the constructed cell. If provided, it must equal
|
||||
``len(digits)``; otherwise it is inferred from the number of digits.
|
||||
|
||||
Returns
|
||||
-------
|
||||
H3Cell
|
||||
The constructed cell.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
'830e8cfffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
'841e057ffffffff'
|
||||
|
||||
>>> construct_cell(15, 0, 0, 5, 3, res=4)
|
||||
'841e057ffffffff'
|
||||
"""
|
||||
if (res is not None) and (len(digits) != res):
|
||||
raise ValueError('Resolution must match number of digits.')
|
||||
|
||||
digits = array('i', digits)
|
||||
o = _cy.construct_cell(base_cell_number, digits)
|
||||
return _out_scalar(o)
|
||||
|
||||
|
||||
def deconstruct_cell(h):
|
||||
"""
|
||||
Deconstruct cell into base cell and digits.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
h : H3Cell
|
||||
Cell to deconstruct.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list of int
|
||||
[base_cell_number, digit1, digit2, ..., digitN]
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> h = construct_cell(7, 2, 1, 4) # resolution 3 cell
|
||||
>>> h
|
||||
'830e8cfffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(7, 2, 1, 4)
|
||||
|
||||
>>> h = construct_cell(15, 0, 0, 5, 3) # resolution 4 cell
|
||||
>>> h
|
||||
'841e057ffffffff'
|
||||
>>> deconstruct_cell(h)
|
||||
(15, 0, 0, 5, 3)
|
||||
>>> construct_cell(*deconstruct_cell(h), 0) == cell_to_center_child(h)
|
||||
"""
|
||||
res = get_resolution(h)
|
||||
bc = get_base_cell_number(h)
|
||||
digits = [get_index_digit(h, r + 1) for r in range(res)]
|
||||
|
||||
return [bc, *digits]
|
||||
|
||||
|
||||
def are_neighbor_cells(h1, h2):
|
||||
"""
|
||||
Returns ``True`` if ``h1`` and ``h2`` are neighboring cells.
|
||||
|
||||
-396
@@ -1,396 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# import apis into sdk package
|
||||
from influxdb_client.service.authorizations_service import AuthorizationsService
|
||||
from influxdb_client.service.backup_service import BackupService
|
||||
from influxdb_client.service.bucket_schemas_service import BucketSchemasService
|
||||
from influxdb_client.service.buckets_service import BucketsService
|
||||
from influxdb_client.service.cells_service import CellsService
|
||||
from influxdb_client.service.checks_service import ChecksService
|
||||
from influxdb_client.service.config_service import ConfigService
|
||||
from influxdb_client.service.dbr_ps_service import DBRPsService
|
||||
from influxdb_client.service.dashboards_service import DashboardsService
|
||||
from influxdb_client.service.delete_service import DeleteService
|
||||
from influxdb_client.service.health_service import HealthService
|
||||
from influxdb_client.service.invokable_scripts_service import InvokableScriptsService
|
||||
from influxdb_client.service.labels_service import LabelsService
|
||||
from influxdb_client.service.legacy_authorizations_service import LegacyAuthorizationsService
|
||||
from influxdb_client.service.metrics_service import MetricsService
|
||||
from influxdb_client.service.notification_endpoints_service import NotificationEndpointsService
|
||||
from influxdb_client.service.notification_rules_service import NotificationRulesService
|
||||
from influxdb_client.service.organizations_service import OrganizationsService
|
||||
from influxdb_client.service.ping_service import PingService
|
||||
from influxdb_client.service.query_service import QueryService
|
||||
from influxdb_client.service.ready_service import ReadyService
|
||||
from influxdb_client.service.remote_connections_service import RemoteConnectionsService
|
||||
from influxdb_client.service.replications_service import ReplicationsService
|
||||
from influxdb_client.service.resources_service import ResourcesService
|
||||
from influxdb_client.service.restore_service import RestoreService
|
||||
from influxdb_client.service.routes_service import RoutesService
|
||||
from influxdb_client.service.rules_service import RulesService
|
||||
from influxdb_client.service.scraper_targets_service import ScraperTargetsService
|
||||
from influxdb_client.service.secrets_service import SecretsService
|
||||
from influxdb_client.service.setup_service import SetupService
|
||||
from influxdb_client.service.signin_service import SigninService
|
||||
from influxdb_client.service.signout_service import SignoutService
|
||||
from influxdb_client.service.sources_service import SourcesService
|
||||
from influxdb_client.service.tasks_service import TasksService
|
||||
from influxdb_client.service.telegraf_plugins_service import TelegrafPluginsService
|
||||
from influxdb_client.service.telegrafs_service import TelegrafsService
|
||||
from influxdb_client.service.templates_service import TemplatesService
|
||||
from influxdb_client.service.users_service import UsersService
|
||||
from influxdb_client.service.variables_service import VariablesService
|
||||
from influxdb_client.service.views_service import ViewsService
|
||||
from influxdb_client.service.write_service import WriteService
|
||||
|
||||
from influxdb_client.configuration import Configuration
|
||||
# import models into sdk package
|
||||
from influxdb_client.domain.ast_response import ASTResponse
|
||||
from influxdb_client.domain.add_resource_member_request_body import AddResourceMemberRequestBody
|
||||
from influxdb_client.domain.analyze_query_response import AnalyzeQueryResponse
|
||||
from influxdb_client.domain.analyze_query_response_errors import AnalyzeQueryResponseErrors
|
||||
from influxdb_client.domain.array_expression import ArrayExpression
|
||||
from influxdb_client.domain.authorization import Authorization
|
||||
from influxdb_client.domain.authorization_post_request import AuthorizationPostRequest
|
||||
from influxdb_client.domain.authorization_update_request import AuthorizationUpdateRequest
|
||||
from influxdb_client.domain.authorizations import Authorizations
|
||||
from influxdb_client.domain.axes import Axes
|
||||
from influxdb_client.domain.axis import Axis
|
||||
from influxdb_client.domain.axis_scale import AxisScale
|
||||
from influxdb_client.domain.bad_statement import BadStatement
|
||||
from influxdb_client.domain.band_view_properties import BandViewProperties
|
||||
from influxdb_client.domain.binary_expression import BinaryExpression
|
||||
from influxdb_client.domain.block import Block
|
||||
from influxdb_client.domain.boolean_literal import BooleanLiteral
|
||||
from influxdb_client.domain.bucket import Bucket
|
||||
from influxdb_client.domain.bucket_links import BucketLinks
|
||||
from influxdb_client.domain.bucket_metadata_manifest import BucketMetadataManifest
|
||||
from influxdb_client.domain.bucket_retention_rules import BucketRetentionRules
|
||||
from influxdb_client.domain.bucket_shard_mapping import BucketShardMapping
|
||||
from influxdb_client.domain.buckets import Buckets
|
||||
from influxdb_client.domain.builder_aggregate_function_type import BuilderAggregateFunctionType
|
||||
from influxdb_client.domain.builder_config import BuilderConfig
|
||||
from influxdb_client.domain.builder_config_aggregate_window import BuilderConfigAggregateWindow
|
||||
from influxdb_client.domain.builder_functions_type import BuilderFunctionsType
|
||||
from influxdb_client.domain.builder_tags_type import BuilderTagsType
|
||||
from influxdb_client.domain.builtin_statement import BuiltinStatement
|
||||
from influxdb_client.domain.call_expression import CallExpression
|
||||
from influxdb_client.domain.cell import Cell
|
||||
from influxdb_client.domain.cell_links import CellLinks
|
||||
from influxdb_client.domain.cell_update import CellUpdate
|
||||
from influxdb_client.domain.cell_with_view_properties import CellWithViewProperties
|
||||
from influxdb_client.domain.check import Check
|
||||
from influxdb_client.domain.check_base import CheckBase
|
||||
from influxdb_client.domain.check_base_links import CheckBaseLinks
|
||||
from influxdb_client.domain.check_discriminator import CheckDiscriminator
|
||||
from influxdb_client.domain.check_patch import CheckPatch
|
||||
from influxdb_client.domain.check_status_level import CheckStatusLevel
|
||||
from influxdb_client.domain.check_view_properties import CheckViewProperties
|
||||
from influxdb_client.domain.checks import Checks
|
||||
from influxdb_client.domain.column_data_type import ColumnDataType
|
||||
from influxdb_client.domain.column_semantic_type import ColumnSemanticType
|
||||
from influxdb_client.domain.conditional_expression import ConditionalExpression
|
||||
from influxdb_client.domain.config import Config
|
||||
from influxdb_client.domain.constant_variable_properties import ConstantVariableProperties
|
||||
from influxdb_client.domain.create_cell import CreateCell
|
||||
from influxdb_client.domain.create_dashboard_request import CreateDashboardRequest
|
||||
from influxdb_client.domain.custom_check import CustomCheck
|
||||
from influxdb_client.domain.dbrp import DBRP
|
||||
from influxdb_client.domain.dbrp_create import DBRPCreate
|
||||
from influxdb_client.domain.dbrp_get import DBRPGet
|
||||
from influxdb_client.domain.dbrp_update import DBRPUpdate
|
||||
from influxdb_client.domain.dbr_ps import DBRPs
|
||||
from influxdb_client.domain.dashboard import Dashboard
|
||||
from influxdb_client.domain.dashboard_color import DashboardColor
|
||||
from influxdb_client.domain.dashboard_query import DashboardQuery
|
||||
from influxdb_client.domain.dashboard_with_view_properties import DashboardWithViewProperties
|
||||
from influxdb_client.domain.dashboards import Dashboards
|
||||
from influxdb_client.domain.date_time_literal import DateTimeLiteral
|
||||
from influxdb_client.domain.deadman_check import DeadmanCheck
|
||||
from influxdb_client.domain.decimal_places import DecimalPlaces
|
||||
from influxdb_client.domain.delete_predicate_request import DeletePredicateRequest
|
||||
from influxdb_client.domain.dialect import Dialect
|
||||
from influxdb_client.domain.dict_expression import DictExpression
|
||||
from influxdb_client.domain.dict_item import DictItem
|
||||
from influxdb_client.domain.duration import Duration
|
||||
from influxdb_client.domain.duration_literal import DurationLiteral
|
||||
from influxdb_client.domain.error import Error
|
||||
from influxdb_client.domain.expression import Expression
|
||||
from influxdb_client.domain.expression_statement import ExpressionStatement
|
||||
from influxdb_client.domain.field import Field
|
||||
from influxdb_client.domain.file import File
|
||||
from influxdb_client.domain.float_literal import FloatLiteral
|
||||
from influxdb_client.domain.flux_response import FluxResponse
|
||||
from influxdb_client.domain.flux_suggestion import FluxSuggestion
|
||||
from influxdb_client.domain.flux_suggestions import FluxSuggestions
|
||||
from influxdb_client.domain.function_expression import FunctionExpression
|
||||
from influxdb_client.domain.gauge_view_properties import GaugeViewProperties
|
||||
from influxdb_client.domain.greater_threshold import GreaterThreshold
|
||||
from influxdb_client.domain.http_notification_endpoint import HTTPNotificationEndpoint
|
||||
from influxdb_client.domain.http_notification_rule import HTTPNotificationRule
|
||||
from influxdb_client.domain.http_notification_rule_base import HTTPNotificationRuleBase
|
||||
from influxdb_client.domain.health_check import HealthCheck
|
||||
from influxdb_client.domain.heatmap_view_properties import HeatmapViewProperties
|
||||
from influxdb_client.domain.histogram_view_properties import HistogramViewProperties
|
||||
from influxdb_client.domain.identifier import Identifier
|
||||
from influxdb_client.domain.import_declaration import ImportDeclaration
|
||||
from influxdb_client.domain.index_expression import IndexExpression
|
||||
from influxdb_client.domain.integer_literal import IntegerLiteral
|
||||
from influxdb_client.domain.is_onboarding import IsOnboarding
|
||||
from influxdb_client.domain.label import Label
|
||||
from influxdb_client.domain.label_create_request import LabelCreateRequest
|
||||
from influxdb_client.domain.label_mapping import LabelMapping
|
||||
from influxdb_client.domain.label_response import LabelResponse
|
||||
from influxdb_client.domain.label_update import LabelUpdate
|
||||
from influxdb_client.domain.labels_response import LabelsResponse
|
||||
from influxdb_client.domain.language_request import LanguageRequest
|
||||
from influxdb_client.domain.legacy_authorization_post_request import LegacyAuthorizationPostRequest
|
||||
from influxdb_client.domain.lesser_threshold import LesserThreshold
|
||||
from influxdb_client.domain.line_plus_single_stat_properties import LinePlusSingleStatProperties
|
||||
from influxdb_client.domain.line_protocol_error import LineProtocolError
|
||||
from influxdb_client.domain.line_protocol_length_error import LineProtocolLengthError
|
||||
from influxdb_client.domain.links import Links
|
||||
from influxdb_client.domain.list_stacks_response import ListStacksResponse
|
||||
from influxdb_client.domain.log_event import LogEvent
|
||||
from influxdb_client.domain.logical_expression import LogicalExpression
|
||||
from influxdb_client.domain.logs import Logs
|
||||
from influxdb_client.domain.map_variable_properties import MapVariableProperties
|
||||
from influxdb_client.domain.markdown_view_properties import MarkdownViewProperties
|
||||
from influxdb_client.domain.measurement_schema import MeasurementSchema
|
||||
from influxdb_client.domain.measurement_schema_column import MeasurementSchemaColumn
|
||||
from influxdb_client.domain.measurement_schema_create_request import MeasurementSchemaCreateRequest
|
||||
from influxdb_client.domain.measurement_schema_list import MeasurementSchemaList
|
||||
from influxdb_client.domain.measurement_schema_update_request import MeasurementSchemaUpdateRequest
|
||||
from influxdb_client.domain.member_assignment import MemberAssignment
|
||||
from influxdb_client.domain.member_expression import MemberExpression
|
||||
from influxdb_client.domain.metadata_backup import MetadataBackup
|
||||
from influxdb_client.domain.model_property import ModelProperty
|
||||
from influxdb_client.domain.mosaic_view_properties import MosaicViewProperties
|
||||
from influxdb_client.domain.node import Node
|
||||
from influxdb_client.domain.notification_endpoint import NotificationEndpoint
|
||||
from influxdb_client.domain.notification_endpoint_base import NotificationEndpointBase
|
||||
from influxdb_client.domain.notification_endpoint_base_links import NotificationEndpointBaseLinks
|
||||
from influxdb_client.domain.notification_endpoint_discriminator import NotificationEndpointDiscriminator
|
||||
from influxdb_client.domain.notification_endpoint_type import NotificationEndpointType
|
||||
from influxdb_client.domain.notification_endpoint_update import NotificationEndpointUpdate
|
||||
from influxdb_client.domain.notification_endpoints import NotificationEndpoints
|
||||
from influxdb_client.domain.notification_rule import NotificationRule
|
||||
from influxdb_client.domain.notification_rule_base import NotificationRuleBase
|
||||
from influxdb_client.domain.notification_rule_base_links import NotificationRuleBaseLinks
|
||||
from influxdb_client.domain.notification_rule_discriminator import NotificationRuleDiscriminator
|
||||
from influxdb_client.domain.notification_rule_update import NotificationRuleUpdate
|
||||
from influxdb_client.domain.notification_rules import NotificationRules
|
||||
from influxdb_client.domain.object_expression import ObjectExpression
|
||||
from influxdb_client.domain.onboarding_request import OnboardingRequest
|
||||
from influxdb_client.domain.onboarding_response import OnboardingResponse
|
||||
from influxdb_client.domain.option_statement import OptionStatement
|
||||
from influxdb_client.domain.organization import Organization
|
||||
from influxdb_client.domain.organization_links import OrganizationLinks
|
||||
from influxdb_client.domain.organizations import Organizations
|
||||
from influxdb_client.domain.package import Package
|
||||
from influxdb_client.domain.package_clause import PackageClause
|
||||
from influxdb_client.domain.pager_duty_notification_endpoint import PagerDutyNotificationEndpoint
|
||||
from influxdb_client.domain.pager_duty_notification_rule import PagerDutyNotificationRule
|
||||
from influxdb_client.domain.pager_duty_notification_rule_base import PagerDutyNotificationRuleBase
|
||||
from influxdb_client.domain.paren_expression import ParenExpression
|
||||
from influxdb_client.domain.password_reset_body import PasswordResetBody
|
||||
from influxdb_client.domain.patch_bucket_request import PatchBucketRequest
|
||||
from influxdb_client.domain.patch_dashboard_request import PatchDashboardRequest
|
||||
from influxdb_client.domain.patch_organization_request import PatchOrganizationRequest
|
||||
from influxdb_client.domain.patch_retention_rule import PatchRetentionRule
|
||||
from influxdb_client.domain.patch_stack_request import PatchStackRequest
|
||||
from influxdb_client.domain.patch_stack_request_additional_resources import PatchStackRequestAdditionalResources
|
||||
from influxdb_client.domain.permission import Permission
|
||||
from influxdb_client.domain.permission_resource import PermissionResource
|
||||
from influxdb_client.domain.pipe_expression import PipeExpression
|
||||
from influxdb_client.domain.pipe_literal import PipeLiteral
|
||||
from influxdb_client.domain.post_bucket_request import PostBucketRequest
|
||||
from influxdb_client.domain.post_check import PostCheck
|
||||
from influxdb_client.domain.post_notification_endpoint import PostNotificationEndpoint
|
||||
from influxdb_client.domain.post_notification_rule import PostNotificationRule
|
||||
from influxdb_client.domain.post_organization_request import PostOrganizationRequest
|
||||
from influxdb_client.domain.post_restore_kv_response import PostRestoreKVResponse
|
||||
from influxdb_client.domain.post_stack_request import PostStackRequest
|
||||
from influxdb_client.domain.property_key import PropertyKey
|
||||
from influxdb_client.domain.query import Query
|
||||
from influxdb_client.domain.query_edit_mode import QueryEditMode
|
||||
from influxdb_client.domain.query_variable_properties import QueryVariableProperties
|
||||
from influxdb_client.domain.query_variable_properties_values import QueryVariablePropertiesValues
|
||||
from influxdb_client.domain.range_threshold import RangeThreshold
|
||||
from influxdb_client.domain.ready import Ready
|
||||
from influxdb_client.domain.regexp_literal import RegexpLiteral
|
||||
from influxdb_client.domain.remote_connection import RemoteConnection
|
||||
from influxdb_client.domain.remote_connection_creation_request import RemoteConnectionCreationRequest
|
||||
from influxdb_client.domain.remote_connection_update_request import RemoteConnectionUpdateRequest
|
||||
from influxdb_client.domain.remote_connections import RemoteConnections
|
||||
from influxdb_client.domain.renamable_field import RenamableField
|
||||
from influxdb_client.domain.replication import Replication
|
||||
from influxdb_client.domain.replication_creation_request import ReplicationCreationRequest
|
||||
from influxdb_client.domain.replication_update_request import ReplicationUpdateRequest
|
||||
from influxdb_client.domain.replications import Replications
|
||||
from influxdb_client.domain.resource_member import ResourceMember
|
||||
from influxdb_client.domain.resource_members import ResourceMembers
|
||||
from influxdb_client.domain.resource_members_links import ResourceMembersLinks
|
||||
from influxdb_client.domain.resource_owner import ResourceOwner
|
||||
from influxdb_client.domain.resource_owners import ResourceOwners
|
||||
from influxdb_client.domain.restored_bucket_mappings import RestoredBucketMappings
|
||||
from influxdb_client.domain.retention_policy_manifest import RetentionPolicyManifest
|
||||
from influxdb_client.domain.return_statement import ReturnStatement
|
||||
from influxdb_client.domain.routes import Routes
|
||||
from influxdb_client.domain.routes_external import RoutesExternal
|
||||
from influxdb_client.domain.routes_query import RoutesQuery
|
||||
from influxdb_client.domain.routes_system import RoutesSystem
|
||||
from influxdb_client.domain.rule_status_level import RuleStatusLevel
|
||||
from influxdb_client.domain.run import Run
|
||||
from influxdb_client.domain.run_links import RunLinks
|
||||
from influxdb_client.domain.run_manually import RunManually
|
||||
from influxdb_client.domain.runs import Runs
|
||||
from influxdb_client.domain.smtp_notification_rule import SMTPNotificationRule
|
||||
from influxdb_client.domain.smtp_notification_rule_base import SMTPNotificationRuleBase
|
||||
from influxdb_client.domain.scatter_view_properties import ScatterViewProperties
|
||||
from influxdb_client.domain.schema_type import SchemaType
|
||||
from influxdb_client.domain.scraper_target_request import ScraperTargetRequest
|
||||
from influxdb_client.domain.scraper_target_response import ScraperTargetResponse
|
||||
from influxdb_client.domain.scraper_target_responses import ScraperTargetResponses
|
||||
from influxdb_client.domain.script import Script
|
||||
from influxdb_client.domain.script_create_request import ScriptCreateRequest
|
||||
from influxdb_client.domain.script_invocation_params import ScriptInvocationParams
|
||||
from influxdb_client.domain.script_language import ScriptLanguage
|
||||
from influxdb_client.domain.script_update_request import ScriptUpdateRequest
|
||||
from influxdb_client.domain.scripts import Scripts
|
||||
from influxdb_client.domain.secret_keys import SecretKeys
|
||||
from influxdb_client.domain.secret_keys_response import SecretKeysResponse
|
||||
from influxdb_client.domain.shard_group_manifest import ShardGroupManifest
|
||||
from influxdb_client.domain.shard_manifest import ShardManifest
|
||||
from influxdb_client.domain.shard_owner import ShardOwner
|
||||
from influxdb_client.domain.simple_table_view_properties import SimpleTableViewProperties
|
||||
from influxdb_client.domain.single_stat_view_properties import SingleStatViewProperties
|
||||
from influxdb_client.domain.slack_notification_endpoint import SlackNotificationEndpoint
|
||||
from influxdb_client.domain.slack_notification_rule import SlackNotificationRule
|
||||
from influxdb_client.domain.slack_notification_rule_base import SlackNotificationRuleBase
|
||||
from influxdb_client.domain.source import Source
|
||||
from influxdb_client.domain.source_links import SourceLinks
|
||||
from influxdb_client.domain.sources import Sources
|
||||
from influxdb_client.domain.stack import Stack
|
||||
from influxdb_client.domain.stack_associations import StackAssociations
|
||||
from influxdb_client.domain.stack_events import StackEvents
|
||||
from influxdb_client.domain.stack_links import StackLinks
|
||||
from influxdb_client.domain.stack_resources import StackResources
|
||||
from influxdb_client.domain.statement import Statement
|
||||
from influxdb_client.domain.static_legend import StaticLegend
|
||||
from influxdb_client.domain.status_rule import StatusRule
|
||||
from influxdb_client.domain.string_literal import StringLiteral
|
||||
from influxdb_client.domain.subscription_manifest import SubscriptionManifest
|
||||
from influxdb_client.domain.table_view_properties import TableViewProperties
|
||||
from influxdb_client.domain.table_view_properties_table_options import TableViewPropertiesTableOptions
|
||||
from influxdb_client.domain.tag_rule import TagRule
|
||||
from influxdb_client.domain.task import Task
|
||||
from influxdb_client.domain.task_create_request import TaskCreateRequest
|
||||
from influxdb_client.domain.task_links import TaskLinks
|
||||
from influxdb_client.domain.task_status_type import TaskStatusType
|
||||
from influxdb_client.domain.task_update_request import TaskUpdateRequest
|
||||
from influxdb_client.domain.tasks import Tasks
|
||||
from influxdb_client.domain.telegraf import Telegraf
|
||||
from influxdb_client.domain.telegraf_plugin import TelegrafPlugin
|
||||
from influxdb_client.domain.telegraf_plugin_request import TelegrafPluginRequest
|
||||
from influxdb_client.domain.telegraf_plugin_request_plugins import TelegrafPluginRequestPlugins
|
||||
from influxdb_client.domain.telegraf_plugins import TelegrafPlugins
|
||||
from influxdb_client.domain.telegraf_request import TelegrafRequest
|
||||
from influxdb_client.domain.telegraf_request_metadata import TelegrafRequestMetadata
|
||||
from influxdb_client.domain.telegrafs import Telegrafs
|
||||
from influxdb_client.domain.telegram_notification_endpoint import TelegramNotificationEndpoint
|
||||
from influxdb_client.domain.telegram_notification_rule import TelegramNotificationRule
|
||||
from influxdb_client.domain.telegram_notification_rule_base import TelegramNotificationRuleBase
|
||||
from influxdb_client.domain.template_apply import TemplateApply
|
||||
from influxdb_client.domain.template_apply_remotes import TemplateApplyRemotes
|
||||
from influxdb_client.domain.template_apply_template import TemplateApplyTemplate
|
||||
from influxdb_client.domain.template_chart import TemplateChart
|
||||
from influxdb_client.domain.template_export_by_id import TemplateExportByID
|
||||
from influxdb_client.domain.template_export_by_id_org_ids import TemplateExportByIDOrgIDs
|
||||
from influxdb_client.domain.template_export_by_id_resource_filters import TemplateExportByIDResourceFilters
|
||||
from influxdb_client.domain.template_export_by_id_resources import TemplateExportByIDResources
|
||||
from influxdb_client.domain.template_kind import TemplateKind
|
||||
from influxdb_client.domain.template_summary import TemplateSummary
|
||||
from influxdb_client.domain.template_summary_diff import TemplateSummaryDiff
|
||||
from influxdb_client.domain.template_summary_diff_buckets import TemplateSummaryDiffBuckets
|
||||
from influxdb_client.domain.template_summary_diff_buckets_new_old import TemplateSummaryDiffBucketsNewOld
|
||||
from influxdb_client.domain.template_summary_diff_checks import TemplateSummaryDiffChecks
|
||||
from influxdb_client.domain.template_summary_diff_dashboards import TemplateSummaryDiffDashboards
|
||||
from influxdb_client.domain.template_summary_diff_dashboards_new_old import TemplateSummaryDiffDashboardsNewOld
|
||||
from influxdb_client.domain.template_summary_diff_label_mappings import TemplateSummaryDiffLabelMappings
|
||||
from influxdb_client.domain.template_summary_diff_labels import TemplateSummaryDiffLabels
|
||||
from influxdb_client.domain.template_summary_diff_labels_new_old import TemplateSummaryDiffLabelsNewOld
|
||||
from influxdb_client.domain.template_summary_diff_notification_endpoints import TemplateSummaryDiffNotificationEndpoints
|
||||
from influxdb_client.domain.template_summary_diff_notification_rules import TemplateSummaryDiffNotificationRules
|
||||
from influxdb_client.domain.template_summary_diff_notification_rules_new_old import TemplateSummaryDiffNotificationRulesNewOld
|
||||
from influxdb_client.domain.template_summary_diff_tasks import TemplateSummaryDiffTasks
|
||||
from influxdb_client.domain.template_summary_diff_tasks_new_old import TemplateSummaryDiffTasksNewOld
|
||||
from influxdb_client.domain.template_summary_diff_telegraf_configs import TemplateSummaryDiffTelegrafConfigs
|
||||
from influxdb_client.domain.template_summary_diff_variables import TemplateSummaryDiffVariables
|
||||
from influxdb_client.domain.template_summary_diff_variables_new_old import TemplateSummaryDiffVariablesNewOld
|
||||
from influxdb_client.domain.template_summary_errors import TemplateSummaryErrors
|
||||
from influxdb_client.domain.template_summary_label import TemplateSummaryLabel
|
||||
from influxdb_client.domain.template_summary_label_properties import TemplateSummaryLabelProperties
|
||||
from influxdb_client.domain.template_summary_summary import TemplateSummarySummary
|
||||
from influxdb_client.domain.template_summary_summary_buckets import TemplateSummarySummaryBuckets
|
||||
from influxdb_client.domain.template_summary_summary_dashboards import TemplateSummarySummaryDashboards
|
||||
from influxdb_client.domain.template_summary_summary_label_mappings import TemplateSummarySummaryLabelMappings
|
||||
from influxdb_client.domain.template_summary_summary_notification_rules import TemplateSummarySummaryNotificationRules
|
||||
from influxdb_client.domain.template_summary_summary_status_rules import TemplateSummarySummaryStatusRules
|
||||
from influxdb_client.domain.template_summary_summary_tag_rules import TemplateSummarySummaryTagRules
|
||||
from influxdb_client.domain.template_summary_summary_tasks import TemplateSummarySummaryTasks
|
||||
from influxdb_client.domain.template_summary_summary_variables import TemplateSummarySummaryVariables
|
||||
from influxdb_client.domain.test_statement import TestStatement
|
||||
from influxdb_client.domain.threshold import Threshold
|
||||
from influxdb_client.domain.threshold_base import ThresholdBase
|
||||
from influxdb_client.domain.threshold_check import ThresholdCheck
|
||||
from influxdb_client.domain.unary_expression import UnaryExpression
|
||||
from influxdb_client.domain.unsigned_integer_literal import UnsignedIntegerLiteral
|
||||
from influxdb_client.domain.user import User
|
||||
from influxdb_client.domain.user_response import UserResponse
|
||||
from influxdb_client.domain.user_response_links import UserResponseLinks
|
||||
from influxdb_client.domain.users import Users
|
||||
from influxdb_client.domain.variable import Variable
|
||||
from influxdb_client.domain.variable_assignment import VariableAssignment
|
||||
from influxdb_client.domain.variable_links import VariableLinks
|
||||
from influxdb_client.domain.variable_properties import VariableProperties
|
||||
from influxdb_client.domain.variables import Variables
|
||||
from influxdb_client.domain.view import View
|
||||
from influxdb_client.domain.view_links import ViewLinks
|
||||
from influxdb_client.domain.view_properties import ViewProperties
|
||||
from influxdb_client.domain.views import Views
|
||||
from influxdb_client.domain.write_precision import WritePrecision
|
||||
from influxdb_client.domain.xy_geom import XYGeom
|
||||
from influxdb_client.domain.xy_view_properties import XYViewProperties
|
||||
|
||||
from influxdb_client.client.authorizations_api import AuthorizationsApi
|
||||
from influxdb_client.client.bucket_api import BucketsApi
|
||||
from influxdb_client.client.delete_api import DeleteApi
|
||||
from influxdb_client.client.invokable_scripts_api import InvokableScriptsApi
|
||||
from influxdb_client.client.labels_api import LabelsApi
|
||||
from influxdb_client.client.organizations_api import OrganizationsApi
|
||||
from influxdb_client.client.query_api import QueryApi
|
||||
from influxdb_client.client.tasks_api import TasksApi
|
||||
from influxdb_client.client.users_api import UsersApi
|
||||
from influxdb_client.client.write_api import WriteApi, WriteOptions
|
||||
from influxdb_client.client.influxdb_client import InfluxDBClient
|
||||
from influxdb_client.client.logging_handler import InfluxLoggingHandler
|
||||
from influxdb_client.client.write.point import Point
|
||||
|
||||
from influxdb_client.version import VERSION
|
||||
|
||||
__version__ = VERSION
|
||||
@@ -1 +0,0 @@
|
||||
"""Asynchronous REST APIs."""
|
||||
@@ -1,663 +0,0 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from urllib.parse import quote
|
||||
|
||||
import influxdb_client.domain
|
||||
from influxdb_client import SigninService
|
||||
from influxdb_client import SignoutService
|
||||
from influxdb_client._async import rest
|
||||
from influxdb_client.configuration import Configuration
|
||||
from influxdb_client.rest import _requires_create_user_session, _requires_expire_user_session
|
||||
|
||||
|
||||
class ApiClientAsync(object):
|
||||
"""Generic API client for OpenAPI client library Build.
|
||||
|
||||
OpenAPI generic API client. This client handles the client-
|
||||
server communication, and is invariant across implementations. Specifics of
|
||||
the methods and models for each application are generated from the OpenAPI
|
||||
templates.
|
||||
|
||||
NOTE: This class is auto generated by OpenAPI Generator.
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
|
||||
:param configuration: .Configuration object for this client
|
||||
:param header_name: a header to pass when making calls to the API.
|
||||
:param header_value: a header value to pass when making calls to
|
||||
the API.
|
||||
:param cookie: a cookie to include in the header when making calls
|
||||
to the API
|
||||
:param pool_threads: The number of threads to use for async requests
|
||||
to the API. More threads means more concurrent API requests.
|
||||
"""
|
||||
|
||||
PRIMITIVE_TYPES = (float, bool, bytes, str, int)
|
||||
NATIVE_TYPES_MAPPING = {
|
||||
'int': int,
|
||||
'long': int,
|
||||
'float': float,
|
||||
'str': str,
|
||||
'bool': bool,
|
||||
'date': datetime.date,
|
||||
'datetime': datetime.datetime,
|
||||
'object': object,
|
||||
}
|
||||
_pool = None
|
||||
|
||||
def __init__(self, configuration=None, header_name=None, header_value=None,
|
||||
cookie=None, pool_threads=None, **kwargs):
|
||||
"""Initialize generic API client."""
|
||||
if configuration is None:
|
||||
configuration = Configuration()
|
||||
self.configuration = configuration
|
||||
self.pool_threads = pool_threads
|
||||
|
||||
self.rest_client = rest.RESTClientObjectAsync(configuration, **kwargs)
|
||||
self.default_headers = {}
|
||||
if header_name is not None:
|
||||
self.default_headers[header_name] = header_value
|
||||
self.cookie = cookie
|
||||
# Set default User-Agent.
|
||||
from influxdb_client import VERSION
|
||||
self.user_agent = f'influxdb-client-python/{VERSION}'
|
||||
|
||||
async def close(self):
|
||||
"""Dispose api client."""
|
||||
await self._signout()
|
||||
await self.rest_client.close()
|
||||
"""Dispose pools."""
|
||||
if self._pool:
|
||||
self._pool.close()
|
||||
self._pool.join()
|
||||
self._pool = None
|
||||
|
||||
@property
|
||||
def pool(self):
|
||||
"""Create thread pool on first request avoids instantiating unused threadpool for blocking clients."""
|
||||
if self._pool is None:
|
||||
self._pool = ThreadPool(self.pool_threads)
|
||||
return self._pool
|
||||
|
||||
@property
|
||||
def user_agent(self):
|
||||
"""User agent for this API client."""
|
||||
return self.default_headers['User-Agent']
|
||||
|
||||
@user_agent.setter
|
||||
def user_agent(self, value):
|
||||
"""Set User agent for this API client."""
|
||||
self.default_headers['User-Agent'] = value
|
||||
|
||||
def set_default_header(self, header_name, header_value):
|
||||
"""Set HTTP header for this API client."""
|
||||
self.default_headers[header_name] = header_value
|
||||
|
||||
async def __call_api(
|
||||
self, resource_path, method, path_params=None,
|
||||
query_params=None, header_params=None, body=None, post_params=None,
|
||||
files=None, response_type=None, auth_settings=None,
|
||||
_return_http_data_only=None, collection_formats=None,
|
||||
_preload_content=True, _request_timeout=None, urlopen_kw=None):
|
||||
|
||||
config = self.configuration
|
||||
await self._signin(resource_path=resource_path)
|
||||
|
||||
# header parameters
|
||||
header_params = header_params or {}
|
||||
config.update_request_header_params(resource_path, header_params)
|
||||
header_params.update(self.default_headers)
|
||||
if self.cookie:
|
||||
header_params['Cookie'] = self.cookie
|
||||
if header_params:
|
||||
header_params = self.sanitize_for_serialization(header_params)
|
||||
header_params = dict(self.parameters_to_tuples(header_params,
|
||||
collection_formats))
|
||||
|
||||
# path parameters
|
||||
if path_params:
|
||||
path_params = self.sanitize_for_serialization(path_params)
|
||||
path_params = self.parameters_to_tuples(path_params,
|
||||
collection_formats)
|
||||
for k, v in path_params:
|
||||
# specified safe chars, encode everything
|
||||
resource_path = resource_path.replace(
|
||||
'{%s}' % k,
|
||||
quote(str(v), safe=config.safe_chars_for_path_param)
|
||||
)
|
||||
|
||||
# query parameters
|
||||
if query_params:
|
||||
query_params = self.sanitize_for_serialization(query_params)
|
||||
query_params = self.parameters_to_tuples(query_params,
|
||||
collection_formats)
|
||||
|
||||
# post parameters
|
||||
if post_params or files:
|
||||
post_params = self.prepare_post_parameters(post_params, files)
|
||||
post_params = self.sanitize_for_serialization(post_params)
|
||||
post_params = self.parameters_to_tuples(post_params,
|
||||
collection_formats)
|
||||
|
||||
# auth setting
|
||||
self.update_params_for_auth(header_params, query_params, auth_settings)
|
||||
|
||||
# body
|
||||
if body:
|
||||
body = self.sanitize_for_serialization(body)
|
||||
body = config.update_request_body(resource_path, body)
|
||||
|
||||
# request url
|
||||
url = self.configuration.host + resource_path
|
||||
|
||||
urlopen_kw = urlopen_kw or {}
|
||||
|
||||
# perform request and return response
|
||||
response_data = await self.request(
|
||||
method, url, query_params=query_params, headers=header_params,
|
||||
post_params=post_params, body=body,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout, **urlopen_kw)
|
||||
|
||||
self.last_response = response_data
|
||||
|
||||
return_data = response_data
|
||||
if _preload_content:
|
||||
# deserialize response data
|
||||
if response_type:
|
||||
return_data = self.deserialize(response_data, response_type)
|
||||
else:
|
||||
return_data = None
|
||||
|
||||
if _return_http_data_only is not False:
|
||||
return return_data
|
||||
else:
|
||||
return (return_data, response_data.status,
|
||||
response_data.getheaders())
|
||||
|
||||
def sanitize_for_serialization(self, obj):
|
||||
"""Build a JSON POST object.
|
||||
|
||||
If obj is None, return None.
|
||||
If obj is str, int, long, float, bool, return directly.
|
||||
If obj is datetime.datetime, datetime.date
|
||||
convert to string in iso8601 format.
|
||||
If obj is list, sanitize each element in the list.
|
||||
If obj is dict, return the dict.
|
||||
If obj is OpenAPI model, return the properties dict.
|
||||
|
||||
:param obj: The data to serialize.
|
||||
:return: The serialized form of data.
|
||||
"""
|
||||
if obj is None:
|
||||
return None
|
||||
elif isinstance(obj, self.PRIMITIVE_TYPES):
|
||||
return obj
|
||||
elif isinstance(obj, list):
|
||||
return [self.sanitize_for_serialization(sub_obj)
|
||||
for sub_obj in obj]
|
||||
elif isinstance(obj, tuple):
|
||||
return tuple(self.sanitize_for_serialization(sub_obj)
|
||||
for sub_obj in obj)
|
||||
elif isinstance(obj, (datetime.datetime, datetime.date)):
|
||||
return obj.isoformat()
|
||||
|
||||
if isinstance(obj, dict):
|
||||
obj_dict = obj
|
||||
else:
|
||||
# Convert model obj to dict except
|
||||
# attributes `openapi_types`, `attribute_map`
|
||||
# and attributes which value is not None.
|
||||
# Convert attribute name to json key in
|
||||
# model definition for request.
|
||||
obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
|
||||
for attr, _ in obj.openapi_types.items()
|
||||
if getattr(obj, attr) is not None}
|
||||
|
||||
return {key: self.sanitize_for_serialization(val)
|
||||
for key, val in obj_dict.items()}
|
||||
|
||||
def deserialize(self, response, response_type):
|
||||
"""Deserializes response into an object.
|
||||
|
||||
:param response: RESTResponse object to be deserialized.
|
||||
:param response_type: class literal for
|
||||
deserialized object, or string of class name.
|
||||
|
||||
:return: deserialized object.
|
||||
"""
|
||||
# handle file downloading
|
||||
# save response body into a tmp file and return the instance
|
||||
if response_type == "file":
|
||||
return self.__deserialize_file(response)
|
||||
|
||||
# fetch data from response object
|
||||
try:
|
||||
data = json.loads(response.data)
|
||||
except ValueError:
|
||||
data = response.data
|
||||
|
||||
return self.__deserialize(data, response_type)
|
||||
|
||||
def __deserialize(self, data, klass):
|
||||
"""Deserializes dict, list, str into an object.
|
||||
|
||||
:param data: dict, list or str.
|
||||
:param klass: class literal, or string of class name.
|
||||
|
||||
:return: object.
|
||||
"""
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if type(klass) == str:
|
||||
if klass.startswith('list['):
|
||||
sub_kls = re.match(r'list\[(.*)\]', klass).group(1)
|
||||
return [self.__deserialize(sub_data, sub_kls)
|
||||
for sub_data in data]
|
||||
|
||||
if klass.startswith('dict('):
|
||||
sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2)
|
||||
return {k: self.__deserialize(v, sub_kls)
|
||||
for k, v in data.items()}
|
||||
|
||||
# convert str to class
|
||||
if klass in self.NATIVE_TYPES_MAPPING:
|
||||
klass = self.NATIVE_TYPES_MAPPING[klass]
|
||||
else:
|
||||
klass = getattr(influxdb_client.domain, klass)
|
||||
|
||||
if klass in self.PRIMITIVE_TYPES:
|
||||
return self.__deserialize_primitive(data, klass)
|
||||
elif klass == object:
|
||||
return self.__deserialize_object(data)
|
||||
elif klass == datetime.date:
|
||||
return self.__deserialize_date(data)
|
||||
elif klass == datetime.datetime:
|
||||
return self.__deserialize_datatime(data)
|
||||
else:
|
||||
return self.__deserialize_model(data, klass)
|
||||
|
||||
def call_api(self, resource_path, method,
|
||||
path_params=None, query_params=None, header_params=None,
|
||||
body=None, post_params=None, files=None,
|
||||
response_type=None, auth_settings=None, async_req=None,
|
||||
_return_http_data_only=None, collection_formats=None,
|
||||
_preload_content=True, _request_timeout=None, urlopen_kw=None):
|
||||
"""Make the HTTP request (synchronous) and Return deserialized data.
|
||||
|
||||
To make an async_req request, set the async_req parameter.
|
||||
|
||||
:param resource_path: Path to method endpoint.
|
||||
:param method: Method to call.
|
||||
:param path_params: Path parameters in the url.
|
||||
:param query_params: Query parameters in the url.
|
||||
:param header_params: Header parameters to be
|
||||
placed in the request header.
|
||||
:param body: Request body.
|
||||
:param post_params dict: Request post form parameters,
|
||||
for `application/x-www-form-urlencoded`, `multipart/form-data`.
|
||||
:param auth_settings list: Auth Settings names for the request.
|
||||
:param response: Response data type.
|
||||
:param files dict: key -> filename, value -> filepath,
|
||||
for `multipart/form-data`.
|
||||
:param async_req bool: execute request asynchronously
|
||||
:param _return_http_data_only: response data without head status code
|
||||
and headers
|
||||
:param collection_formats: dict of collection formats for path, query,
|
||||
header, and post parameters.
|
||||
:param _preload_content: if False, the urllib3.HTTPResponse object will
|
||||
be returned without reading/decoding response
|
||||
data. Default is True.
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:param urlopen_kw: Additional parameters are passed to
|
||||
:meth:`urllib3.request.RequestMethods.request`
|
||||
:return:
|
||||
If async_req parameter is True,
|
||||
the request will be called asynchronously.
|
||||
The method will return the request thread.
|
||||
If parameter async_req is False or missing,
|
||||
then the method will return the response directly.
|
||||
"""
|
||||
if not async_req:
|
||||
return self.__call_api(resource_path, method,
|
||||
path_params, query_params, header_params,
|
||||
body, post_params, files,
|
||||
response_type, auth_settings,
|
||||
_return_http_data_only, collection_formats,
|
||||
_preload_content, _request_timeout, urlopen_kw)
|
||||
else:
|
||||
thread = self.pool.apply_async(self.__call_api, (resource_path,
|
||||
method, path_params, query_params,
|
||||
header_params, body,
|
||||
post_params, files,
|
||||
response_type, auth_settings,
|
||||
_return_http_data_only,
|
||||
collection_formats,
|
||||
_preload_content, _request_timeout, urlopen_kw))
|
||||
return thread
|
||||
|
||||
def request(self, method, url, query_params=None, headers=None,
|
||||
post_params=None, body=None, _preload_content=True,
|
||||
_request_timeout=None, **urlopen_kw):
|
||||
"""Make the HTTP request using RESTClient."""
|
||||
if method == "GET":
|
||||
return self.rest_client.GET(url,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif method == "HEAD":
|
||||
return self.rest_client.HEAD(url,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif method == "OPTIONS":
|
||||
return self.rest_client.OPTIONS(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "POST":
|
||||
return self.rest_client.POST(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "PUT":
|
||||
return self.rest_client.PUT(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "PATCH":
|
||||
return self.rest_client.PATCH(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "DELETE":
|
||||
return self.rest_client.DELETE(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
else:
|
||||
raise ValueError(
|
||||
"http method must be `GET`, `HEAD`, `OPTIONS`,"
|
||||
" `POST`, `PATCH`, `PUT` or `DELETE`."
|
||||
)
|
||||
|
||||
def parameters_to_tuples(self, params, collection_formats):
|
||||
"""Get parameters as list of tuples, formatting collections.
|
||||
|
||||
:param params: Parameters as dict or list of two-tuples
|
||||
:param dict collection_formats: Parameter collection formats
|
||||
:return: Parameters as list of tuples, collections formatted
|
||||
"""
|
||||
new_params = []
|
||||
if collection_formats is None:
|
||||
collection_formats = {}
|
||||
for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501
|
||||
if k in collection_formats:
|
||||
collection_format = collection_formats[k]
|
||||
if collection_format == 'multi':
|
||||
new_params.extend((k, value) for value in v)
|
||||
else:
|
||||
if collection_format == 'ssv':
|
||||
delimiter = ' '
|
||||
elif collection_format == 'tsv':
|
||||
delimiter = '\t'
|
||||
elif collection_format == 'pipes':
|
||||
delimiter = '|'
|
||||
else: # csv is the default
|
||||
delimiter = ','
|
||||
new_params.append(
|
||||
(k, delimiter.join(str(value) for value in v)))
|
||||
else:
|
||||
new_params.append((k, v))
|
||||
return new_params
|
||||
|
||||
def prepare_post_parameters(self, post_params=None, files=None):
|
||||
"""Build form parameters.
|
||||
|
||||
:param post_params: Normal form parameters.
|
||||
:param files: File parameters.
|
||||
:return: Form parameters with files.
|
||||
"""
|
||||
params = []
|
||||
|
||||
if post_params:
|
||||
params = post_params
|
||||
|
||||
if files:
|
||||
for k, v in files.items():
|
||||
if not v:
|
||||
continue
|
||||
file_names = v if type(v) is list else [v]
|
||||
for n in file_names:
|
||||
with open(n, 'rb') as f:
|
||||
filename = os.path.basename(f.name)
|
||||
filedata = f.read()
|
||||
mimetype = (mimetypes.guess_type(filename)[0] or
|
||||
'application/octet-stream')
|
||||
params.append(
|
||||
tuple([k, tuple([filename, filedata, mimetype])]))
|
||||
|
||||
return params
|
||||
|
||||
def select_header_accept(self, accepts):
|
||||
"""Return `Accept` based on an array of accepts provided.
|
||||
|
||||
:param accepts: List of headers.
|
||||
:return: Accept (e.g. application/json).
|
||||
"""
|
||||
if not accepts:
|
||||
return
|
||||
|
||||
accepts = [x.lower() for x in accepts]
|
||||
|
||||
if 'application/json' in accepts:
|
||||
return 'application/json'
|
||||
else:
|
||||
return ', '.join(accepts)
|
||||
|
||||
def select_header_content_type(self, content_types):
|
||||
"""Return `Content-Type` based on an array of content_types provided.
|
||||
|
||||
:param content_types: List of content-types.
|
||||
:return: Content-Type (e.g. application/json).
|
||||
"""
|
||||
if not content_types:
|
||||
return 'application/json'
|
||||
|
||||
content_types = [x.lower() for x in content_types]
|
||||
|
||||
if 'application/json' in content_types or '*/*' in content_types:
|
||||
return 'application/json'
|
||||
else:
|
||||
return content_types[0]
|
||||
|
||||
def update_params_for_auth(self, headers, querys, auth_settings):
|
||||
"""Update header and query params based on authentication setting.
|
||||
|
||||
:param headers: Header parameters dict to be updated.
|
||||
:param querys: Query parameters tuple list to be updated.
|
||||
:param auth_settings: Authentication setting identifiers list.
|
||||
"""
|
||||
if not auth_settings:
|
||||
return
|
||||
|
||||
for auth in auth_settings:
|
||||
auth_setting = self.configuration.auth_settings().get(auth)
|
||||
if auth_setting:
|
||||
if not auth_setting['value']:
|
||||
continue
|
||||
elif auth_setting['in'] == 'header':
|
||||
headers[auth_setting['key']] = auth_setting['value']
|
||||
elif auth_setting['in'] == 'query':
|
||||
querys.append((auth_setting['key'], auth_setting['value']))
|
||||
else:
|
||||
raise ValueError(
|
||||
'Authentication token must be in `query` or `header`'
|
||||
)
|
||||
|
||||
def __deserialize_file(self, response):
|
||||
"""Deserializes body to file.
|
||||
|
||||
Saves response body into a file in a temporary folder,
|
||||
using the filename from the `Content-Disposition` header if provided.
|
||||
|
||||
:param response: RESTResponse.
|
||||
:return: file path.
|
||||
"""
|
||||
fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
|
||||
os.close(fd)
|
||||
os.remove(path)
|
||||
|
||||
content_disposition = response.getheader("Content-Disposition")
|
||||
if content_disposition:
|
||||
filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
|
||||
content_disposition).group(1)
|
||||
path = os.path.join(os.path.dirname(path), filename)
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(response.data)
|
||||
|
||||
return path
|
||||
|
||||
def __deserialize_primitive(self, data, klass):
|
||||
"""Deserializes string to primitive type.
|
||||
|
||||
:param data: str.
|
||||
:param klass: class literal.
|
||||
|
||||
:return: int, long, float, str, bool.
|
||||
"""
|
||||
try:
|
||||
return klass(data)
|
||||
except UnicodeEncodeError:
|
||||
return str(data)
|
||||
except TypeError:
|
||||
return data
|
||||
|
||||
def __deserialize_object(self, value):
|
||||
"""Return an original value.
|
||||
|
||||
:return: object.
|
||||
"""
|
||||
return value
|
||||
|
||||
def __deserialize_date(self, string):
|
||||
"""Deserializes string to date.
|
||||
|
||||
:param string: str.
|
||||
:return: date.
|
||||
"""
|
||||
try:
|
||||
from dateutil.parser import parse
|
||||
return parse(string).date()
|
||||
except ImportError:
|
||||
return string
|
||||
except ValueError:
|
||||
raise rest.ApiException(
|
||||
status=0,
|
||||
reason="Failed to parse `{0}` as date object".format(string)
|
||||
)
|
||||
|
||||
def __deserialize_datatime(self, string):
|
||||
"""Deserializes string to datetime.
|
||||
|
||||
The string should be in iso8601 datetime format.
|
||||
|
||||
:param string: str.
|
||||
:return: datetime.
|
||||
"""
|
||||
try:
|
||||
from dateutil.parser import parse
|
||||
return parse(string)
|
||||
except ImportError:
|
||||
return string
|
||||
except ValueError:
|
||||
raise rest.ApiException(
|
||||
status=0,
|
||||
reason=(
|
||||
"Failed to parse `{0}` as datetime object"
|
||||
.format(string)
|
||||
)
|
||||
)
|
||||
|
||||
def __deserialize_model(self, data, klass):
|
||||
"""Deserializes list or dict to model.
|
||||
|
||||
:param data: dict, list.
|
||||
:param klass: class literal.
|
||||
:return: model object.
|
||||
"""
|
||||
if not klass.openapi_types and not hasattr(klass,
|
||||
'get_real_child_model'):
|
||||
return data
|
||||
|
||||
kwargs = {}
|
||||
if klass.openapi_types is not None:
|
||||
for attr, attr_type in klass.openapi_types.items():
|
||||
if (data is not None and
|
||||
klass.attribute_map[attr] in data and
|
||||
isinstance(data, (list, dict))):
|
||||
value = data[klass.attribute_map[attr]]
|
||||
kwargs[attr] = self.__deserialize(value, attr_type)
|
||||
|
||||
instance = klass(**kwargs)
|
||||
|
||||
if hasattr(instance, 'get_real_child_model'):
|
||||
klass_name = instance.get_real_child_model(data)
|
||||
if klass_name:
|
||||
instance = self.__deserialize(data, klass_name)
|
||||
return instance
|
||||
|
||||
async def _signin(self, resource_path: str):
|
||||
if _requires_create_user_session(self.configuration, self.cookie, resource_path):
|
||||
http_info = await SigninService(self).post_signin_async(_return_http_data_only=False)
|
||||
self.cookie = http_info[2]['set-cookie']
|
||||
|
||||
async def _signout(self):
|
||||
if _requires_expire_user_session(self.configuration, self.cookie):
|
||||
await SignoutService(self).post_signout_async()
|
||||
self.cookie = None
|
||||
@@ -1,309 +0,0 @@
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import ssl
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import aiohttp
|
||||
|
||||
from influxdb_client.rest import ApiException
|
||||
from influxdb_client.rest import _BaseRESTClient
|
||||
from influxdb_client.rest import _UTF_8_encoding
|
||||
|
||||
|
||||
async def _on_request_start(session, trace_config_ctx, params):
|
||||
_BaseRESTClient.log_request(params.method, params.url)
|
||||
_BaseRESTClient.log_headers(params.headers, '>>>')
|
||||
|
||||
|
||||
async def _on_request_chunk_sent(session, context, params):
|
||||
if params.chunk:
|
||||
_BaseRESTClient.log_body(params.chunk, '>>>')
|
||||
|
||||
|
||||
async def _on_request_end(session, trace_config_ctx, params):
|
||||
_BaseRESTClient.log_response(params.response.status)
|
||||
_BaseRESTClient.log_headers(params.headers, '<<<')
|
||||
|
||||
response_content = params.response.content
|
||||
data = bytearray()
|
||||
while True:
|
||||
chunk = await response_content.read(100)
|
||||
if not chunk:
|
||||
break
|
||||
data += chunk
|
||||
if data:
|
||||
_BaseRESTClient.log_body(data.decode(_UTF_8_encoding), '<<<')
|
||||
response_content.unread_data(data=data)
|
||||
|
||||
|
||||
class RESTResponseAsync(io.IOBase):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, resp, data):
|
||||
"""Initialize with HTTP response."""
|
||||
self.aiohttp_response = resp
|
||||
self.status = resp.status
|
||||
self.reason = resp.reason
|
||||
self.data = data
|
||||
|
||||
def getheaders(self):
|
||||
"""Return a CIMultiDictProxy of the response headers."""
|
||||
return self.aiohttp_response.headers
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
"""Return a given response header."""
|
||||
return self.aiohttp_response.headers.get(name, default)
|
||||
|
||||
|
||||
class RESTClientObjectAsync(object):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, configuration, pools_size=4, maxsize=None, **kwargs):
|
||||
"""Initialize REST client."""
|
||||
# maxsize is number of requests to host that are allowed in parallel
|
||||
if maxsize is None:
|
||||
maxsize = configuration.connection_pool_maxsize
|
||||
|
||||
if configuration.ssl_context is None:
|
||||
ssl_context = ssl.create_default_context(cafile=configuration.ssl_ca_cert)
|
||||
if configuration.cert_file:
|
||||
ssl_context.load_cert_chain(
|
||||
certfile=configuration.cert_file, keyfile=configuration.cert_key_file,
|
||||
password=configuration.cert_key_password
|
||||
)
|
||||
|
||||
if not configuration.verify_ssl:
|
||||
ssl_context.check_hostname = False
|
||||
ssl_context.verify_mode = ssl.CERT_NONE
|
||||
else:
|
||||
ssl_context = configuration.ssl_context
|
||||
|
||||
connector = aiohttp.TCPConnector(
|
||||
limit=maxsize,
|
||||
ssl=ssl_context
|
||||
)
|
||||
|
||||
self.proxy = configuration.proxy
|
||||
self.proxy_headers = configuration.proxy_headers
|
||||
self.allow_redirects = kwargs.get('allow_redirects', True)
|
||||
self.max_redirects = kwargs.get('max_redirects', 10)
|
||||
|
||||
# configure tracing
|
||||
trace_config = aiohttp.TraceConfig()
|
||||
trace_config.on_request_start.append(_on_request_start)
|
||||
trace_config.on_request_chunk_sent.append(_on_request_chunk_sent)
|
||||
trace_config.on_request_end.append(_on_request_end)
|
||||
|
||||
# timeout
|
||||
if isinstance(configuration.timeout, (int, float,)): # noqa: E501,F821
|
||||
timeout = aiohttp.ClientTimeout(total=configuration.timeout / 1_000)
|
||||
elif isinstance(configuration.timeout, aiohttp.ClientTimeout):
|
||||
timeout = configuration.timeout
|
||||
else:
|
||||
timeout = aiohttp.client.DEFAULT_TIMEOUT
|
||||
|
||||
# https pool manager
|
||||
_client_session_type = kwargs.get('client_session_type', aiohttp.ClientSession)
|
||||
_client_session_kwargs = kwargs.get('client_session_kwargs', {})
|
||||
self.pool_manager = _client_session_type(
|
||||
connector=connector,
|
||||
timeout=timeout,
|
||||
trace_configs=[trace_config] if configuration.debug else None,
|
||||
**_client_session_kwargs
|
||||
)
|
||||
|
||||
async def close(self):
|
||||
"""Dispose connection pool manager."""
|
||||
await self.pool_manager.close()
|
||||
|
||||
async def request(self, method, url, query_params=None, headers=None,
|
||||
body=None, post_params=None, _preload_content=True,
|
||||
_request_timeout=None):
|
||||
"""Execute request.
|
||||
|
||||
:param method: http request method
|
||||
:param url: http request url
|
||||
:param query_params: query parameters in the url
|
||||
:param headers: http request headers
|
||||
:param body: request json body, for `application/json`
|
||||
:param post_params: request post parameters,
|
||||
`application/x-www-form-urlencoded`
|
||||
and `multipart/form-data`
|
||||
:param _preload_content: this is a non-applicable field for
|
||||
the AiohttpClient.
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
"""
|
||||
method = method.upper()
|
||||
assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
|
||||
'PATCH', 'OPTIONS']
|
||||
|
||||
if post_params and body:
|
||||
raise ValueError(
|
||||
"body parameter cannot be used with post_params parameter."
|
||||
)
|
||||
|
||||
post_params = post_params or {}
|
||||
headers = headers or {}
|
||||
|
||||
if 'Content-Type' not in headers:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
|
||||
args = {
|
||||
"method": method,
|
||||
"url": url,
|
||||
"headers": headers,
|
||||
"allow_redirects": self.allow_redirects,
|
||||
"max_redirects": self.max_redirects
|
||||
}
|
||||
|
||||
if self.proxy:
|
||||
args["proxy"] = self.proxy
|
||||
if self.proxy_headers:
|
||||
args["proxy_headers"] = self.proxy_headers
|
||||
|
||||
if query_params:
|
||||
args["url"] += '?' + urlencode(query_params)
|
||||
|
||||
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
|
||||
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
|
||||
if re.search('json', headers['Content-Type'], re.IGNORECASE):
|
||||
if body is not None:
|
||||
body = json.dumps(body)
|
||||
args["data"] = body
|
||||
elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501
|
||||
args["data"] = aiohttp.FormData(post_params)
|
||||
elif headers['Content-Type'] == 'multipart/form-data':
|
||||
# must del headers['Content-Type'], or the correct
|
||||
# Content-Type which generated by aiohttp
|
||||
del headers['Content-Type']
|
||||
data = aiohttp.FormData()
|
||||
for param in post_params:
|
||||
k, v = param
|
||||
if isinstance(v, tuple) and len(v) == 3:
|
||||
data.add_field(k,
|
||||
value=v[1],
|
||||
filename=v[0],
|
||||
content_type=v[2])
|
||||
else:
|
||||
data.add_field(k, v)
|
||||
args["data"] = data
|
||||
|
||||
# Pass a `bytes` parameter directly in the body to support
|
||||
# other content types than Json when `body` argument is provided
|
||||
# in serialized form
|
||||
elif isinstance(body, bytes):
|
||||
args["data"] = body
|
||||
else:
|
||||
# Cannot generate the request from given parameters
|
||||
msg = """Cannot prepare a request message for provided
|
||||
arguments. Please check that your arguments match
|
||||
declared content type."""
|
||||
raise ApiException(status=0, reason=msg)
|
||||
|
||||
r = await self.pool_manager.request(**args)
|
||||
if _preload_content:
|
||||
|
||||
data = await r.read()
|
||||
r = RESTResponseAsync(r, data)
|
||||
|
||||
if not 200 <= r.status <= 299:
|
||||
raise ApiException(http_resp=r)
|
||||
|
||||
return r
|
||||
|
||||
async def GET(self, url, headers=None, query_params=None,
|
||||
_preload_content=True, _request_timeout=None):
|
||||
"""Perform GET HTTP request."""
|
||||
return (await self.request("GET", url,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
query_params=query_params))
|
||||
|
||||
async def HEAD(self, url, headers=None, query_params=None,
|
||||
_preload_content=True, _request_timeout=None):
|
||||
"""Perform HEAD HTTP request."""
|
||||
return (await self.request("HEAD", url,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
query_params=query_params))
|
||||
|
||||
async def OPTIONS(self, url, headers=None, query_params=None,
|
||||
post_params=None, body=None, _preload_content=True,
|
||||
_request_timeout=None):
|
||||
"""Perform OPTIONS HTTP request."""
|
||||
return (await self.request("OPTIONS", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body))
|
||||
|
||||
async def DELETE(self, url, headers=None, query_params=None, body=None,
|
||||
_preload_content=True, _request_timeout=None):
|
||||
"""Perform DELETE HTTP request."""
|
||||
return (await self.request("DELETE", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body))
|
||||
|
||||
async def POST(self, url, headers=None, query_params=None,
|
||||
post_params=None, body=None, _preload_content=True,
|
||||
_request_timeout=None):
|
||||
"""Perform POST HTTP request."""
|
||||
return (await self.request("POST", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body))
|
||||
|
||||
async def PUT(self, url, headers=None, query_params=None, post_params=None,
|
||||
body=None, _preload_content=True, _request_timeout=None):
|
||||
"""Perform PUT HTTP request."""
|
||||
return (await self.request("PUT", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body))
|
||||
|
||||
async def PATCH(self, url, headers=None, query_params=None,
|
||||
post_params=None, body=None, _preload_content=True,
|
||||
_request_timeout=None):
|
||||
"""Perform PATCH HTTP request."""
|
||||
return (await self.request("PATCH", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body))
|
||||
@@ -1 +0,0 @@
|
||||
"""Synchronous REST APIs."""
|
||||
@@ -1,663 +0,0 @@
|
||||
# coding: utf-8
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from multiprocessing.pool import ThreadPool
|
||||
from urllib.parse import quote
|
||||
|
||||
import influxdb_client.domain
|
||||
from influxdb_client import SigninService
|
||||
from influxdb_client import SignoutService
|
||||
from influxdb_client._sync import rest
|
||||
from influxdb_client.configuration import Configuration
|
||||
from influxdb_client.rest import _requires_create_user_session, _requires_expire_user_session
|
||||
|
||||
|
||||
class ApiClient(object):
|
||||
"""Generic API client for OpenAPI client library Build.
|
||||
|
||||
OpenAPI generic API client. This client handles the client-
|
||||
server communication, and is invariant across implementations. Specifics of
|
||||
the methods and models for each application are generated from the OpenAPI
|
||||
templates.
|
||||
|
||||
NOTE: This class is auto generated by OpenAPI Generator.
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
|
||||
:param configuration: .Configuration object for this client
|
||||
:param header_name: a header to pass when making calls to the API.
|
||||
:param header_value: a header value to pass when making calls to
|
||||
the API.
|
||||
:param cookie: a cookie to include in the header when making calls
|
||||
to the API
|
||||
:param pool_threads: The number of threads to use for async requests
|
||||
to the API. More threads means more concurrent API requests.
|
||||
"""
|
||||
|
||||
PRIMITIVE_TYPES = (float, bool, bytes, str, int)
|
||||
NATIVE_TYPES_MAPPING = {
|
||||
'int': int,
|
||||
'long': int,
|
||||
'float': float,
|
||||
'str': str,
|
||||
'bool': bool,
|
||||
'date': datetime.date,
|
||||
'datetime': datetime.datetime,
|
||||
'object': object,
|
||||
}
|
||||
_pool = None
|
||||
|
||||
def __init__(self, configuration=None, header_name=None, header_value=None,
|
||||
cookie=None, pool_threads=None, retries=False):
|
||||
"""Initialize generic API client."""
|
||||
if configuration is None:
|
||||
configuration = Configuration()
|
||||
self.configuration = configuration
|
||||
self.pool_threads = pool_threads
|
||||
|
||||
self.rest_client = rest.RESTClientObject(configuration, retries=retries)
|
||||
self.default_headers = {}
|
||||
if header_name is not None:
|
||||
self.default_headers[header_name] = header_value
|
||||
self.cookie = cookie
|
||||
# Set default User-Agent.
|
||||
from influxdb_client import VERSION
|
||||
self.user_agent = f'influxdb-client-python/{VERSION}'
|
||||
|
||||
def __del__(self):
|
||||
"""Dispose pools."""
|
||||
self._signout()
|
||||
if self._pool:
|
||||
self._pool.close()
|
||||
self._pool.join()
|
||||
self._pool = None
|
||||
if self.rest_client and self.rest_client.pool_manager and hasattr(self.rest_client.pool_manager, 'clear'):
|
||||
self.rest_client.pool_manager.clear()
|
||||
|
||||
@property
|
||||
def pool(self):
|
||||
"""Create thread pool on first request avoids instantiating unused threadpool for blocking clients."""
|
||||
if self._pool is None:
|
||||
self._pool = ThreadPool(self.pool_threads)
|
||||
return self._pool
|
||||
|
||||
@property
|
||||
def user_agent(self):
|
||||
"""User agent for this API client."""
|
||||
return self.default_headers['User-Agent']
|
||||
|
||||
@user_agent.setter
|
||||
def user_agent(self, value):
|
||||
"""Set User agent for this API client."""
|
||||
self.default_headers['User-Agent'] = value
|
||||
|
||||
def set_default_header(self, header_name, header_value):
|
||||
"""Set HTTP header for this API client."""
|
||||
self.default_headers[header_name] = header_value
|
||||
|
||||
def __call_api(
|
||||
self, resource_path, method, path_params=None,
|
||||
query_params=None, header_params=None, body=None, post_params=None,
|
||||
files=None, response_type=None, auth_settings=None,
|
||||
_return_http_data_only=None, collection_formats=None,
|
||||
_preload_content=True, _request_timeout=None, urlopen_kw=None):
|
||||
|
||||
config = self.configuration
|
||||
self._signin(resource_path=resource_path)
|
||||
|
||||
# header parameters
|
||||
header_params = header_params or {}
|
||||
config.update_request_header_params(resource_path, header_params)
|
||||
header_params.update(self.default_headers)
|
||||
if self.cookie:
|
||||
header_params['Cookie'] = self.cookie
|
||||
if header_params:
|
||||
header_params = self.sanitize_for_serialization(header_params)
|
||||
header_params = dict(self.parameters_to_tuples(header_params,
|
||||
collection_formats))
|
||||
|
||||
# path parameters
|
||||
if path_params:
|
||||
path_params = self.sanitize_for_serialization(path_params)
|
||||
path_params = self.parameters_to_tuples(path_params,
|
||||
collection_formats)
|
||||
for k, v in path_params:
|
||||
# specified safe chars, encode everything
|
||||
resource_path = resource_path.replace(
|
||||
'{%s}' % k,
|
||||
quote(str(v), safe=config.safe_chars_for_path_param)
|
||||
)
|
||||
|
||||
# query parameters
|
||||
if query_params:
|
||||
query_params = self.sanitize_for_serialization(query_params)
|
||||
query_params = self.parameters_to_tuples(query_params,
|
||||
collection_formats)
|
||||
|
||||
# post parameters
|
||||
if post_params or files:
|
||||
post_params = self.prepare_post_parameters(post_params, files)
|
||||
post_params = self.sanitize_for_serialization(post_params)
|
||||
post_params = self.parameters_to_tuples(post_params,
|
||||
collection_formats)
|
||||
|
||||
# auth setting
|
||||
self.update_params_for_auth(header_params, query_params, auth_settings)
|
||||
|
||||
# body
|
||||
if body:
|
||||
body = self.sanitize_for_serialization(body)
|
||||
body = config.update_request_body(resource_path, body)
|
||||
|
||||
# request url
|
||||
url = self.configuration.host + resource_path
|
||||
|
||||
urlopen_kw = urlopen_kw or {}
|
||||
|
||||
# perform request and return response
|
||||
response_data = self.request(
|
||||
method, url, query_params=query_params, headers=header_params,
|
||||
post_params=post_params, body=body,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout, **urlopen_kw)
|
||||
|
||||
self.last_response = response_data
|
||||
|
||||
return_data = response_data
|
||||
if _preload_content:
|
||||
# deserialize response data
|
||||
if response_type:
|
||||
return_data = self.deserialize(response_data, response_type)
|
||||
else:
|
||||
return_data = None
|
||||
|
||||
if _return_http_data_only:
|
||||
return (return_data)
|
||||
else:
|
||||
return (return_data, response_data.status,
|
||||
response_data.getheaders())
|
||||
|
||||
def sanitize_for_serialization(self, obj):
|
||||
"""Build a JSON POST object.
|
||||
|
||||
If obj is None, return None.
|
||||
If obj is str, int, long, float, bool, return directly.
|
||||
If obj is datetime.datetime, datetime.date
|
||||
convert to string in iso8601 format.
|
||||
If obj is list, sanitize each element in the list.
|
||||
If obj is dict, return the dict.
|
||||
If obj is OpenAPI model, return the properties dict.
|
||||
|
||||
:param obj: The data to serialize.
|
||||
:return: The serialized form of data.
|
||||
"""
|
||||
if obj is None:
|
||||
return None
|
||||
elif isinstance(obj, self.PRIMITIVE_TYPES):
|
||||
return obj
|
||||
elif isinstance(obj, list):
|
||||
return [self.sanitize_for_serialization(sub_obj)
|
||||
for sub_obj in obj]
|
||||
elif isinstance(obj, tuple):
|
||||
return tuple(self.sanitize_for_serialization(sub_obj)
|
||||
for sub_obj in obj)
|
||||
elif isinstance(obj, (datetime.datetime, datetime.date)):
|
||||
return obj.isoformat()
|
||||
|
||||
if isinstance(obj, dict):
|
||||
obj_dict = obj
|
||||
else:
|
||||
# Convert model obj to dict except
|
||||
# attributes `openapi_types`, `attribute_map`
|
||||
# and attributes which value is not None.
|
||||
# Convert attribute name to json key in
|
||||
# model definition for request.
|
||||
obj_dict = {obj.attribute_map[attr]: getattr(obj, attr)
|
||||
for attr, _ in obj.openapi_types.items()
|
||||
if getattr(obj, attr) is not None}
|
||||
|
||||
return {key: self.sanitize_for_serialization(val)
|
||||
for key, val in obj_dict.items()}
|
||||
|
||||
def deserialize(self, response, response_type):
|
||||
"""Deserializes response into an object.
|
||||
|
||||
:param response: RESTResponse object to be deserialized.
|
||||
:param response_type: class literal for
|
||||
deserialized object, or string of class name.
|
||||
|
||||
:return: deserialized object.
|
||||
"""
|
||||
# handle file downloading
|
||||
# save response body into a tmp file and return the instance
|
||||
if response_type == "file":
|
||||
return self.__deserialize_file(response)
|
||||
|
||||
# fetch data from response object
|
||||
try:
|
||||
data = json.loads(response.data)
|
||||
except ValueError:
|
||||
data = response.data
|
||||
|
||||
return self.__deserialize(data, response_type)
|
||||
|
||||
def __deserialize(self, data, klass):
|
||||
"""Deserializes dict, list, str into an object.
|
||||
|
||||
:param data: dict, list or str.
|
||||
:param klass: class literal, or string of class name.
|
||||
|
||||
:return: object.
|
||||
"""
|
||||
if data is None:
|
||||
return None
|
||||
|
||||
if type(klass) == str:
|
||||
if klass.startswith('list['):
|
||||
sub_kls = re.match(r'list\[(.*)\]', klass).group(1)
|
||||
return [self.__deserialize(sub_data, sub_kls)
|
||||
for sub_data in data]
|
||||
|
||||
if klass.startswith('dict('):
|
||||
sub_kls = re.match(r'dict\(([^,]*), (.*)\)', klass).group(2)
|
||||
return {k: self.__deserialize(v, sub_kls)
|
||||
for k, v in data.items()}
|
||||
|
||||
# convert str to class
|
||||
if klass in self.NATIVE_TYPES_MAPPING:
|
||||
klass = self.NATIVE_TYPES_MAPPING[klass]
|
||||
else:
|
||||
klass = getattr(influxdb_client.domain, klass)
|
||||
|
||||
if klass in self.PRIMITIVE_TYPES:
|
||||
return self.__deserialize_primitive(data, klass)
|
||||
elif klass == object:
|
||||
return self.__deserialize_object(data)
|
||||
elif klass == datetime.date:
|
||||
return self.__deserialize_date(data)
|
||||
elif klass == datetime.datetime:
|
||||
return self.__deserialize_datatime(data)
|
||||
else:
|
||||
return self.__deserialize_model(data, klass)
|
||||
|
||||
def call_api(self, resource_path, method,
|
||||
path_params=None, query_params=None, header_params=None,
|
||||
body=None, post_params=None, files=None,
|
||||
response_type=None, auth_settings=None, async_req=None,
|
||||
_return_http_data_only=None, collection_formats=None,
|
||||
_preload_content=True, _request_timeout=None, urlopen_kw=None):
|
||||
"""Make the HTTP request (synchronous) and Return deserialized data.
|
||||
|
||||
To make an async_req request, set the async_req parameter.
|
||||
|
||||
:param resource_path: Path to method endpoint.
|
||||
:param method: Method to call.
|
||||
:param path_params: Path parameters in the url.
|
||||
:param query_params: Query parameters in the url.
|
||||
:param header_params: Header parameters to be
|
||||
placed in the request header.
|
||||
:param body: Request body.
|
||||
:param post_params dict: Request post form parameters,
|
||||
for `application/x-www-form-urlencoded`, `multipart/form-data`.
|
||||
:param auth_settings list: Auth Settings names for the request.
|
||||
:param response: Response data type.
|
||||
:param files dict: key -> filename, value -> filepath,
|
||||
for `multipart/form-data`.
|
||||
:param async_req bool: execute request asynchronously
|
||||
:param _return_http_data_only: response data without head status code
|
||||
and headers
|
||||
:param collection_formats: dict of collection formats for path, query,
|
||||
header, and post parameters.
|
||||
:param _preload_content: if False, the urllib3.HTTPResponse object will
|
||||
be returned without reading/decoding response
|
||||
data. Default is True.
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:param urlopen_kw: Additional parameters are passed to
|
||||
:meth:`urllib3.request.RequestMethods.request`
|
||||
:return:
|
||||
If async_req parameter is True,
|
||||
the request will be called asynchronously.
|
||||
The method will return the request thread.
|
||||
If parameter async_req is False or missing,
|
||||
then the method will return the response directly.
|
||||
"""
|
||||
if not async_req:
|
||||
return self.__call_api(resource_path, method,
|
||||
path_params, query_params, header_params,
|
||||
body, post_params, files,
|
||||
response_type, auth_settings,
|
||||
_return_http_data_only, collection_formats,
|
||||
_preload_content, _request_timeout, urlopen_kw)
|
||||
else:
|
||||
thread = self.pool.apply_async(self.__call_api, (resource_path,
|
||||
method, path_params, query_params,
|
||||
header_params, body,
|
||||
post_params, files,
|
||||
response_type, auth_settings,
|
||||
_return_http_data_only,
|
||||
collection_formats,
|
||||
_preload_content, _request_timeout, urlopen_kw))
|
||||
return thread
|
||||
|
||||
def request(self, method, url, query_params=None, headers=None,
|
||||
post_params=None, body=None, _preload_content=True,
|
||||
_request_timeout=None, **urlopen_kw):
|
||||
"""Make the HTTP request using RESTClient."""
|
||||
if method == "GET":
|
||||
return self.rest_client.GET(url,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif method == "HEAD":
|
||||
return self.rest_client.HEAD(url,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif method == "OPTIONS":
|
||||
return self.rest_client.OPTIONS(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "POST":
|
||||
return self.rest_client.POST(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "PUT":
|
||||
return self.rest_client.PUT(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "PATCH":
|
||||
return self.rest_client.PATCH(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
elif method == "DELETE":
|
||||
return self.rest_client.DELETE(url,
|
||||
query_params=query_params,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
else:
|
||||
raise ValueError(
|
||||
"http method must be `GET`, `HEAD`, `OPTIONS`,"
|
||||
" `POST`, `PATCH`, `PUT` or `DELETE`."
|
||||
)
|
||||
|
||||
def parameters_to_tuples(self, params, collection_formats):
|
||||
"""Get parameters as list of tuples, formatting collections.
|
||||
|
||||
:param params: Parameters as dict or list of two-tuples
|
||||
:param dict collection_formats: Parameter collection formats
|
||||
:return: Parameters as list of tuples, collections formatted
|
||||
"""
|
||||
new_params = []
|
||||
if collection_formats is None:
|
||||
collection_formats = {}
|
||||
for k, v in params.items() if isinstance(params, dict) else params: # noqa: E501
|
||||
if k in collection_formats:
|
||||
collection_format = collection_formats[k]
|
||||
if collection_format == 'multi':
|
||||
new_params.extend((k, value) for value in v)
|
||||
else:
|
||||
if collection_format == 'ssv':
|
||||
delimiter = ' '
|
||||
elif collection_format == 'tsv':
|
||||
delimiter = '\t'
|
||||
elif collection_format == 'pipes':
|
||||
delimiter = '|'
|
||||
else: # csv is the default
|
||||
delimiter = ','
|
||||
new_params.append(
|
||||
(k, delimiter.join(str(value) for value in v)))
|
||||
else:
|
||||
new_params.append((k, v))
|
||||
return new_params
|
||||
|
||||
def prepare_post_parameters(self, post_params=None, files=None):
|
||||
"""Build form parameters.
|
||||
|
||||
:param post_params: Normal form parameters.
|
||||
:param files: File parameters.
|
||||
:return: Form parameters with files.
|
||||
"""
|
||||
params = []
|
||||
|
||||
if post_params:
|
||||
params = post_params
|
||||
|
||||
if files:
|
||||
for k, v in files.items():
|
||||
if not v:
|
||||
continue
|
||||
file_names = v if type(v) is list else [v]
|
||||
for n in file_names:
|
||||
with open(n, 'rb') as f:
|
||||
filename = os.path.basename(f.name)
|
||||
filedata = f.read()
|
||||
mimetype = (mimetypes.guess_type(filename)[0] or
|
||||
'application/octet-stream')
|
||||
params.append(
|
||||
tuple([k, tuple([filename, filedata, mimetype])]))
|
||||
|
||||
return params
|
||||
|
||||
def select_header_accept(self, accepts):
|
||||
"""Return `Accept` based on an array of accepts provided.
|
||||
|
||||
:param accepts: List of headers.
|
||||
:return: Accept (e.g. application/json).
|
||||
"""
|
||||
if not accepts:
|
||||
return
|
||||
|
||||
accepts = [x.lower() for x in accepts]
|
||||
|
||||
if 'application/json' in accepts:
|
||||
return 'application/json'
|
||||
else:
|
||||
return ', '.join(accepts)
|
||||
|
||||
def select_header_content_type(self, content_types):
|
||||
"""Return `Content-Type` based on an array of content_types provided.
|
||||
|
||||
:param content_types: List of content-types.
|
||||
:return: Content-Type (e.g. application/json).
|
||||
"""
|
||||
if not content_types:
|
||||
return 'application/json'
|
||||
|
||||
content_types = [x.lower() for x in content_types]
|
||||
|
||||
if 'application/json' in content_types or '*/*' in content_types:
|
||||
return 'application/json'
|
||||
else:
|
||||
return content_types[0]
|
||||
|
||||
def update_params_for_auth(self, headers, querys, auth_settings):
|
||||
"""Update header and query params based on authentication setting.
|
||||
|
||||
:param headers: Header parameters dict to be updated.
|
||||
:param querys: Query parameters tuple list to be updated.
|
||||
:param auth_settings: Authentication setting identifiers list.
|
||||
"""
|
||||
if not auth_settings:
|
||||
return
|
||||
|
||||
for auth in auth_settings:
|
||||
auth_setting = self.configuration.auth_settings().get(auth)
|
||||
if auth_setting:
|
||||
if not auth_setting['value']:
|
||||
continue
|
||||
elif auth_setting['in'] == 'header':
|
||||
headers[auth_setting['key']] = auth_setting['value']
|
||||
elif auth_setting['in'] == 'query':
|
||||
querys.append((auth_setting['key'], auth_setting['value']))
|
||||
else:
|
||||
raise ValueError(
|
||||
'Authentication token must be in `query` or `header`'
|
||||
)
|
||||
|
||||
def __deserialize_file(self, response):
|
||||
"""Deserializes body to file.
|
||||
|
||||
Saves response body into a file in a temporary folder,
|
||||
using the filename from the `Content-Disposition` header if provided.
|
||||
|
||||
:param response: RESTResponse.
|
||||
:return: file path.
|
||||
"""
|
||||
fd, path = tempfile.mkstemp(dir=self.configuration.temp_folder_path)
|
||||
os.close(fd)
|
||||
os.remove(path)
|
||||
|
||||
content_disposition = response.getheader("Content-Disposition")
|
||||
if content_disposition:
|
||||
filename = re.search(r'filename=[\'"]?([^\'"\s]+)[\'"]?',
|
||||
content_disposition).group(1)
|
||||
path = os.path.join(os.path.dirname(path), filename)
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(response.data)
|
||||
|
||||
return path
|
||||
|
||||
def __deserialize_primitive(self, data, klass):
|
||||
"""Deserializes string to primitive type.
|
||||
|
||||
:param data: str.
|
||||
:param klass: class literal.
|
||||
|
||||
:return: int, long, float, str, bool.
|
||||
"""
|
||||
try:
|
||||
return klass(data)
|
||||
except UnicodeEncodeError:
|
||||
return str(data)
|
||||
except TypeError:
|
||||
return data
|
||||
|
||||
def __deserialize_object(self, value):
|
||||
"""Return an original value.
|
||||
|
||||
:return: object.
|
||||
"""
|
||||
return value
|
||||
|
||||
def __deserialize_date(self, string):
|
||||
"""Deserializes string to date.
|
||||
|
||||
:param string: str.
|
||||
:return: date.
|
||||
"""
|
||||
try:
|
||||
from dateutil.parser import parse
|
||||
return parse(string).date()
|
||||
except ImportError:
|
||||
return string
|
||||
except ValueError:
|
||||
raise rest.ApiException(
|
||||
status=0,
|
||||
reason="Failed to parse `{0}` as date object".format(string)
|
||||
)
|
||||
|
||||
def __deserialize_datatime(self, string):
|
||||
"""Deserializes string to datetime.
|
||||
|
||||
The string should be in iso8601 datetime format.
|
||||
|
||||
:param string: str.
|
||||
:return: datetime.
|
||||
"""
|
||||
try:
|
||||
from dateutil.parser import parse
|
||||
return parse(string)
|
||||
except ImportError:
|
||||
return string
|
||||
except ValueError:
|
||||
raise rest.ApiException(
|
||||
status=0,
|
||||
reason=(
|
||||
"Failed to parse `{0}` as datetime object"
|
||||
.format(string)
|
||||
)
|
||||
)
|
||||
|
||||
def __deserialize_model(self, data, klass):
|
||||
"""Deserializes list or dict to model.
|
||||
|
||||
:param data: dict, list.
|
||||
:param klass: class literal.
|
||||
:return: model object.
|
||||
"""
|
||||
if not klass.openapi_types and not hasattr(klass,
|
||||
'get_real_child_model'):
|
||||
return data
|
||||
|
||||
kwargs = {}
|
||||
if klass.openapi_types is not None:
|
||||
for attr, attr_type in klass.openapi_types.items():
|
||||
if (data is not None and
|
||||
klass.attribute_map[attr] in data and
|
||||
isinstance(data, (list, dict))):
|
||||
value = data[klass.attribute_map[attr]]
|
||||
kwargs[attr] = self.__deserialize(value, attr_type)
|
||||
|
||||
instance = klass(**kwargs)
|
||||
|
||||
if hasattr(instance, 'get_real_child_model'):
|
||||
klass_name = instance.get_real_child_model(data)
|
||||
if klass_name:
|
||||
instance = self.__deserialize(data, klass_name)
|
||||
return instance
|
||||
|
||||
def _signin(self, resource_path: str):
|
||||
if _requires_create_user_session(self.configuration, self.cookie, resource_path):
|
||||
http_info = SigninService(self).post_signin_with_http_info()
|
||||
self.cookie = http_info[2]['set-cookie']
|
||||
|
||||
def _signout(self):
|
||||
if _requires_expire_user_session(self.configuration, self.cookie):
|
||||
SignoutService(self).post_signout()
|
||||
self.cookie = None
|
||||
-355
@@ -1,355 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import ssl
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from influxdb_client.rest import ApiException
|
||||
from influxdb_client.rest import _BaseRESTClient
|
||||
|
||||
try:
|
||||
import urllib3
|
||||
except ImportError:
|
||||
raise ImportError('OpenAPI Python client requires urllib3.')
|
||||
|
||||
|
||||
class RESTResponse(io.IOBase):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, resp):
|
||||
"""Initialize with HTTP response."""
|
||||
self.urllib3_response = resp
|
||||
self.status = resp.status
|
||||
self.reason = resp.reason
|
||||
self.data = resp.data
|
||||
|
||||
def getheaders(self):
|
||||
"""Return a dictionary of the response headers."""
|
||||
return self.urllib3_response.headers
|
||||
|
||||
def getheader(self, name, default=None):
|
||||
"""Return a given response header."""
|
||||
return self.urllib3_response.headers.get(name, default)
|
||||
|
||||
|
||||
class RESTClientObject(object):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self, configuration, pools_size=4, maxsize=None, retries=False):
|
||||
"""Initialize REST client."""
|
||||
# urllib3.PoolManager will pass all kw parameters to connectionpool
|
||||
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501
|
||||
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501
|
||||
# maxsize is the number of requests to host that are allowed in parallel # noqa: E501
|
||||
# Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501
|
||||
|
||||
self.configuration = configuration
|
||||
self.pools_size = pools_size
|
||||
self.maxsize = maxsize
|
||||
self.retries = retries
|
||||
|
||||
# cert_reqs
|
||||
if configuration.verify_ssl:
|
||||
cert_reqs = ssl.CERT_REQUIRED
|
||||
else:
|
||||
cert_reqs = ssl.CERT_NONE
|
||||
|
||||
# ca_certs
|
||||
if configuration.ssl_ca_cert:
|
||||
ca_certs = configuration.ssl_ca_cert
|
||||
else:
|
||||
ca_certs = None
|
||||
|
||||
addition_pool_args = {}
|
||||
if configuration.assert_hostname is not None:
|
||||
addition_pool_args['assert_hostname'] = configuration.assert_hostname # noqa: E501
|
||||
addition_pool_args['retries'] = self.retries
|
||||
|
||||
if maxsize is None:
|
||||
if configuration.connection_pool_maxsize is not None:
|
||||
maxsize = configuration.connection_pool_maxsize
|
||||
else:
|
||||
maxsize = 4
|
||||
|
||||
# https pool manager
|
||||
if configuration.proxy:
|
||||
self.pool_manager = urllib3.ProxyManager(
|
||||
num_pools=pools_size,
|
||||
maxsize=maxsize,
|
||||
cert_reqs=cert_reqs,
|
||||
ca_certs=ca_certs,
|
||||
cert_file=configuration.cert_file,
|
||||
key_file=configuration.cert_key_file,
|
||||
key_password=configuration.cert_key_password,
|
||||
proxy_url=configuration.proxy,
|
||||
proxy_headers=configuration.proxy_headers,
|
||||
ssl_context=configuration.ssl_context,
|
||||
**addition_pool_args
|
||||
)
|
||||
else:
|
||||
self.pool_manager = urllib3.PoolManager(
|
||||
num_pools=pools_size,
|
||||
maxsize=maxsize,
|
||||
cert_reqs=cert_reqs,
|
||||
ca_certs=ca_certs,
|
||||
cert_file=configuration.cert_file,
|
||||
key_file=configuration.cert_key_file,
|
||||
key_password=configuration.cert_key_password,
|
||||
ssl_context=configuration.ssl_context,
|
||||
**addition_pool_args
|
||||
)
|
||||
|
||||
def request(self, method, url, query_params=None, headers=None,
|
||||
body=None, post_params=None, _preload_content=True,
|
||||
_request_timeout=None, **urlopen_kw):
|
||||
"""Perform requests.
|
||||
|
||||
:param method: http request method
|
||||
:param url: http request url
|
||||
:param query_params: query parameters in the url
|
||||
:param headers: http request headers
|
||||
:param body: request json body, for `application/json`
|
||||
:param post_params: request post parameters,
|
||||
`application/x-www-form-urlencoded`
|
||||
and `multipart/form-data`
|
||||
:param _preload_content: if False, the urllib3.HTTPResponse object will
|
||||
be returned without reading/decoding response
|
||||
data. Default is True.
|
||||
:param _request_timeout: timeout setting for this request. If one
|
||||
number provided, it will be total request
|
||||
timeout. It can also be a pair (tuple) of
|
||||
(connection, read) timeouts.
|
||||
:param urlopen_kw: Additional parameters are passed to
|
||||
:meth:`urllib3.request.RequestMethods.request`
|
||||
"""
|
||||
method = method.upper()
|
||||
assert method in ['GET', 'HEAD', 'DELETE', 'POST', 'PUT',
|
||||
'PATCH', 'OPTIONS']
|
||||
|
||||
if post_params and body:
|
||||
raise ValueError(
|
||||
"body parameter cannot be used with post_params parameter."
|
||||
)
|
||||
|
||||
post_params = post_params or {}
|
||||
headers = headers or {}
|
||||
|
||||
timeout = None
|
||||
_configured_timeout = _request_timeout or self.configuration.timeout
|
||||
if _configured_timeout:
|
||||
if isinstance(_configured_timeout, (int, float, )): # noqa: E501,F821
|
||||
timeout = urllib3.Timeout(total=_configured_timeout / 1_000)
|
||||
elif (isinstance(_configured_timeout, tuple) and
|
||||
len(_configured_timeout) == 2):
|
||||
timeout = urllib3.Timeout(
|
||||
connect=_configured_timeout[0] / 1_000, read=_configured_timeout[1] / 1_000)
|
||||
|
||||
if 'Content-Type' not in headers:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
|
||||
if self.configuration.debug:
|
||||
_BaseRESTClient.log_request(method, f"{url}{'' if query_params is None else '?' + urlencode(query_params)}")
|
||||
_BaseRESTClient.log_headers(headers, '>>>')
|
||||
_BaseRESTClient.log_body(body, '>>>')
|
||||
|
||||
try:
|
||||
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
|
||||
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
|
||||
if query_params:
|
||||
url += '?' + urlencode(query_params)
|
||||
if re.search('json', headers['Content-Type'], re.IGNORECASE):
|
||||
request_body = None
|
||||
if body is not None:
|
||||
request_body = json.dumps(body)
|
||||
r = self.pool_manager.request(
|
||||
method, url,
|
||||
body=request_body,
|
||||
preload_content=_preload_content,
|
||||
timeout=timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif headers['Content-Type'] == 'application/x-www-form-urlencoded': # noqa: E501
|
||||
r = self.pool_manager.request(
|
||||
method, url,
|
||||
fields=post_params,
|
||||
encode_multipart=False,
|
||||
preload_content=_preload_content,
|
||||
timeout=timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
elif headers['Content-Type'] == 'multipart/form-data':
|
||||
# must del headers['Content-Type'], or the correct
|
||||
# Content-Type which generated by urllib3 will be
|
||||
# overwritten.
|
||||
del headers['Content-Type']
|
||||
r = self.pool_manager.request(
|
||||
method, url,
|
||||
fields=post_params,
|
||||
encode_multipart=True,
|
||||
preload_content=_preload_content,
|
||||
timeout=timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
# Pass a `string` parameter directly in the body to support
|
||||
# other content types than Json when `body` argument is
|
||||
# provided in serialized form
|
||||
elif isinstance(body, str) or isinstance(body, bytes):
|
||||
request_body = body
|
||||
r = self.pool_manager.request(
|
||||
method, url,
|
||||
body=request_body,
|
||||
preload_content=_preload_content,
|
||||
timeout=timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
else:
|
||||
# Cannot generate the request from given parameters
|
||||
msg = """Cannot prepare a request message for provided
|
||||
arguments. Please check that your arguments match
|
||||
declared content type."""
|
||||
raise ApiException(status=0, reason=msg)
|
||||
# For `GET`, `HEAD`
|
||||
else:
|
||||
r = self.pool_manager.request(method, url,
|
||||
fields=query_params,
|
||||
preload_content=_preload_content,
|
||||
timeout=timeout,
|
||||
headers=headers,
|
||||
**urlopen_kw)
|
||||
except urllib3.exceptions.SSLError as e:
|
||||
msg = "{0}\n{1}".format(type(e).__name__, str(e))
|
||||
raise ApiException(status=0, reason=msg)
|
||||
|
||||
if _preload_content:
|
||||
r = RESTResponse(r)
|
||||
|
||||
# In the python 3, the response.data is bytes.
|
||||
# we need to decode it to string.
|
||||
r.data = r.data.decode('utf8')
|
||||
|
||||
if self.configuration.debug:
|
||||
_BaseRESTClient.log_response(r.status)
|
||||
if hasattr(r, 'headers'):
|
||||
_BaseRESTClient.log_headers(r.headers, '<<<')
|
||||
if hasattr(r, 'urllib3_response'):
|
||||
_BaseRESTClient.log_headers(r.urllib3_response.headers, '<<<')
|
||||
_BaseRESTClient.log_body(r.data, '<<<')
|
||||
|
||||
if not 200 <= r.status <= 299:
|
||||
raise ApiException(http_resp=r)
|
||||
|
||||
return r
|
||||
|
||||
def GET(self, url, headers=None, query_params=None, _preload_content=True,
|
||||
_request_timeout=None, **urlopen_kw):
|
||||
"""Perform GET HTTP request."""
|
||||
return self.request("GET", url,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
query_params=query_params,
|
||||
**urlopen_kw)
|
||||
|
||||
def HEAD(self, url, headers=None, query_params=None, _preload_content=True,
|
||||
_request_timeout=None, **urlopen_kw):
|
||||
"""Perform HEAD HTTP request."""
|
||||
return self.request("HEAD", url,
|
||||
headers=headers,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
query_params=query_params,
|
||||
**urlopen_kw)
|
||||
|
||||
def OPTIONS(self, url, headers=None, query_params=None, post_params=None,
|
||||
body=None, _preload_content=True, _request_timeout=None, **urlopen_kw):
|
||||
"""Perform OPTIONS HTTP request."""
|
||||
return self.request("OPTIONS", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
|
||||
def DELETE(self, url, headers=None, query_params=None, body=None,
|
||||
_preload_content=True, _request_timeout=None, **urlopen_kw):
|
||||
"""Perform DELETE HTTP request."""
|
||||
return self.request("DELETE", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
|
||||
def POST(self, url, headers=None, query_params=None, post_params=None,
|
||||
body=None, _preload_content=True, _request_timeout=None, **urlopen_kw):
|
||||
"""Perform POST HTTP request."""
|
||||
return self.request("POST", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
|
||||
def PUT(self, url, headers=None, query_params=None, post_params=None,
|
||||
body=None, _preload_content=True, _request_timeout=None, **urlopen_kw):
|
||||
"""Perform PUT HTTP request."""
|
||||
return self.request("PUT", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
|
||||
def PATCH(self, url, headers=None, query_params=None, post_params=None,
|
||||
body=None, _preload_content=True, _request_timeout=None, **urlopen_kw):
|
||||
"""Perform PATCH HTTP request."""
|
||||
return self.request("PATCH", url,
|
||||
headers=headers,
|
||||
query_params=query_params,
|
||||
post_params=post_params,
|
||||
_preload_content=_preload_content,
|
||||
_request_timeout=_request_timeout,
|
||||
body=body,
|
||||
**urlopen_kw)
|
||||
|
||||
def __getstate__(self):
|
||||
"""Return a dict of attributes that you want to pickle."""
|
||||
state = self.__dict__.copy()
|
||||
# Remove Pool managaer
|
||||
del state['pool_manager']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Set your object with the provided dict."""
|
||||
self.__dict__.update(state)
|
||||
# Init Pool manager
|
||||
self.__init__(self.configuration, self.pools_size, self.maxsize, self.retries)
|
||||
@@ -1,56 +0,0 @@
|
||||
# flake8: noqa
|
||||
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# import apis into api package
|
||||
from influxdb_client.service.authorizations_service import AuthorizationsService
|
||||
from influxdb_client.service.backup_service import BackupService
|
||||
from influxdb_client.service.bucket_schemas_service import BucketSchemasService
|
||||
from influxdb_client.service.buckets_service import BucketsService
|
||||
from influxdb_client.service.cells_service import CellsService
|
||||
from influxdb_client.service.checks_service import ChecksService
|
||||
from influxdb_client.service.config_service import ConfigService
|
||||
from influxdb_client.service.dbr_ps_service import DBRPsService
|
||||
from influxdb_client.service.dashboards_service import DashboardsService
|
||||
from influxdb_client.service.delete_service import DeleteService
|
||||
from influxdb_client.service.health_service import HealthService
|
||||
from influxdb_client.service.invokable_scripts_service import InvokableScriptsService
|
||||
from influxdb_client.service.labels_service import LabelsService
|
||||
from influxdb_client.service.legacy_authorizations_service import LegacyAuthorizationsService
|
||||
from influxdb_client.service.metrics_service import MetricsService
|
||||
from influxdb_client.service.notification_endpoints_service import NotificationEndpointsService
|
||||
from influxdb_client.service.notification_rules_service import NotificationRulesService
|
||||
from influxdb_client.service.organizations_service import OrganizationsService
|
||||
from influxdb_client.service.ping_service import PingService
|
||||
from influxdb_client.service.query_service import QueryService
|
||||
from influxdb_client.service.ready_service import ReadyService
|
||||
from influxdb_client.service.remote_connections_service import RemoteConnectionsService
|
||||
from influxdb_client.service.replications_service import ReplicationsService
|
||||
from influxdb_client.service.resources_service import ResourcesService
|
||||
from influxdb_client.service.restore_service import RestoreService
|
||||
from influxdb_client.service.routes_service import RoutesService
|
||||
from influxdb_client.service.rules_service import RulesService
|
||||
from influxdb_client.service.scraper_targets_service import ScraperTargetsService
|
||||
from influxdb_client.service.secrets_service import SecretsService
|
||||
from influxdb_client.service.setup_service import SetupService
|
||||
from influxdb_client.service.signin_service import SigninService
|
||||
from influxdb_client.service.signout_service import SignoutService
|
||||
from influxdb_client.service.sources_service import SourcesService
|
||||
from influxdb_client.service.tasks_service import TasksService
|
||||
from influxdb_client.service.telegraf_plugins_service import TelegrafPluginsService
|
||||
from influxdb_client.service.telegrafs_service import TelegrafsService
|
||||
from influxdb_client.service.templates_service import TemplatesService
|
||||
from influxdb_client.service.users_service import UsersService
|
||||
from influxdb_client.service.variables_service import VariablesService
|
||||
from influxdb_client.service.views_service import ViewsService
|
||||
from influxdb_client.service.write_service import WriteService
|
||||
@@ -1,556 +0,0 @@
|
||||
"""Commons function for Sync and Async client."""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import base64
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Generator, Any, Union, Iterable, AsyncGenerator
|
||||
|
||||
from urllib3 import HTTPResponse
|
||||
|
||||
from influxdb_client import Configuration, Dialect, Query, OptionStatement, VariableAssignment, Identifier, \
|
||||
Expression, BooleanLiteral, IntegerLiteral, FloatLiteral, DateTimeLiteral, UnaryExpression, DurationLiteral, \
|
||||
Duration, StringLiteral, ArrayExpression, ImportDeclaration, MemberExpression, MemberAssignment, File, \
|
||||
WriteService, QueryService, DeleteService, DeletePredicateRequest
|
||||
from influxdb_client.client.flux_csv_parser import FluxResponseMetadataMode, FluxCsvParser, FluxSerializationMode
|
||||
from influxdb_client.client.flux_table import FluxRecord, TableList, CSVIterator
|
||||
from influxdb_client.client.util.date_utils import get_date_helper
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
from influxdb_client.client.warnings import MissingPivotFunction
|
||||
from influxdb_client.client.write.dataframe_serializer import DataframeSerializer
|
||||
from influxdb_client.rest import _UTF_8_encoding
|
||||
|
||||
try:
|
||||
import dataclasses
|
||||
|
||||
_HAS_DATACLASS = True
|
||||
except ModuleNotFoundError:
|
||||
_HAS_DATACLASS = False
|
||||
|
||||
LOGGERS_NAMES = [
|
||||
'influxdb_client.client.influxdb_client',
|
||||
'influxdb_client.client.influxdb_client_async',
|
||||
'influxdb_client.client.write_api',
|
||||
'influxdb_client.client.write_api_async',
|
||||
'influxdb_client.client.write.retry',
|
||||
'influxdb_client.client.write.dataframe_serializer',
|
||||
'influxdb_client.client.util.multiprocessing_helper',
|
||||
'influxdb_client.client.http',
|
||||
'influxdb_client.client.exceptions',
|
||||
]
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class _BaseClient(object):
|
||||
def __init__(self, url, token, debug=None, timeout=10_000, enable_gzip=False, org: str = None,
|
||||
default_tags: dict = None, http_client_logger: str = None, **kwargs) -> None:
|
||||
self.url = url
|
||||
self.token = token
|
||||
self.org = org
|
||||
|
||||
self.default_tags = default_tags
|
||||
|
||||
self.conf = _Configuration()
|
||||
if not isinstance(self.url, str):
|
||||
raise ValueError('"url" attribute is not str instance')
|
||||
if self.url.endswith("/"):
|
||||
self.conf.host = self.url[:-1]
|
||||
else:
|
||||
self.conf.host = self.url
|
||||
self.conf.enable_gzip = enable_gzip
|
||||
self.conf.verify_ssl = kwargs.get('verify_ssl', True)
|
||||
self.conf.ssl_ca_cert = kwargs.get('ssl_ca_cert', None)
|
||||
self.conf.cert_file = kwargs.get('cert_file', None)
|
||||
self.conf.cert_key_file = kwargs.get('cert_key_file', None)
|
||||
self.conf.cert_key_password = kwargs.get('cert_key_password', None)
|
||||
self.conf.ssl_context = kwargs.get('ssl_context', None)
|
||||
self.conf.proxy = kwargs.get('proxy', None)
|
||||
self.conf.proxy_headers = kwargs.get('proxy_headers', None)
|
||||
self.conf.connection_pool_maxsize = kwargs.get('connection_pool_maxsize', self.conf.connection_pool_maxsize)
|
||||
self.conf.timeout = timeout
|
||||
# logging
|
||||
self.conf.loggers["http_client_logger"] = logging.getLogger(http_client_logger)
|
||||
for client_logger in LOGGERS_NAMES:
|
||||
self.conf.loggers[client_logger] = logging.getLogger(client_logger)
|
||||
self.conf.debug = debug
|
||||
|
||||
self.conf.username = kwargs.get('username', None)
|
||||
self.conf.password = kwargs.get('password', None)
|
||||
# defaults
|
||||
self.auth_header_name = None
|
||||
self.auth_header_value = None
|
||||
# by token
|
||||
if self.token:
|
||||
self.auth_header_name = "Authorization"
|
||||
self.auth_header_value = "Token " + self.token
|
||||
# by HTTP basic
|
||||
auth_basic = kwargs.get('auth_basic', False)
|
||||
if auth_basic:
|
||||
self.auth_header_name = "Authorization"
|
||||
self.auth_header_value = "Basic " + base64.b64encode(token.encode()).decode()
|
||||
# by username, password
|
||||
if self.conf.username and self.conf.password:
|
||||
self.auth_header_name = None
|
||||
self.auth_header_value = None
|
||||
|
||||
self.retries = kwargs.get('retries', False)
|
||||
|
||||
self.profilers = kwargs.get('profilers', None)
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gzip=False, **kwargs):
|
||||
config = configparser.ConfigParser()
|
||||
config_name = kwargs.get('config_name', 'influx2')
|
||||
is_json = False
|
||||
try:
|
||||
config.read(config_file)
|
||||
except configparser.ParsingError:
|
||||
with open(config_file) as json_file:
|
||||
import json
|
||||
config = json.load(json_file)
|
||||
is_json = True
|
||||
|
||||
def _config_value(key: str):
|
||||
value = str(config[key]) if is_json else config[config_name][key]
|
||||
return value.strip('"')
|
||||
|
||||
def _has_option(key: str):
|
||||
return key in config if is_json else config.has_option(config_name, key)
|
||||
|
||||
def _has_section(key: str):
|
||||
return key in config if is_json else config.has_section(key)
|
||||
|
||||
url = _config_value('url')
|
||||
token = _config_value('token')
|
||||
|
||||
timeout = None
|
||||
if _has_option('timeout'):
|
||||
timeout = _config_value('timeout')
|
||||
|
||||
org = None
|
||||
if _has_option('org'):
|
||||
org = _config_value('org')
|
||||
|
||||
verify_ssl = True
|
||||
if _has_option('verify_ssl'):
|
||||
verify_ssl = _config_value('verify_ssl')
|
||||
|
||||
ssl_ca_cert = None
|
||||
if _has_option('ssl_ca_cert'):
|
||||
ssl_ca_cert = _config_value('ssl_ca_cert')
|
||||
|
||||
cert_file = None
|
||||
if _has_option('cert_file'):
|
||||
cert_file = _config_value('cert_file')
|
||||
|
||||
cert_key_file = None
|
||||
if _has_option('cert_key_file'):
|
||||
cert_key_file = _config_value('cert_key_file')
|
||||
|
||||
cert_key_password = None
|
||||
if _has_option('cert_key_password'):
|
||||
cert_key_password = _config_value('cert_key_password')
|
||||
|
||||
connection_pool_maxsize = None
|
||||
if _has_option('connection_pool_maxsize'):
|
||||
connection_pool_maxsize = _config_value('connection_pool_maxsize')
|
||||
|
||||
auth_basic = False
|
||||
if _has_option('auth_basic'):
|
||||
auth_basic = _config_value('auth_basic')
|
||||
|
||||
default_tags = None
|
||||
if _has_section('tags'):
|
||||
if is_json:
|
||||
default_tags = config['tags']
|
||||
else:
|
||||
tags = {k: v.strip('"') for k, v in config.items('tags')}
|
||||
default_tags = dict(tags)
|
||||
|
||||
profilers = None
|
||||
if _has_option('profilers'):
|
||||
profilers = [x.strip() for x in _config_value('profilers').split(',')]
|
||||
|
||||
proxy = None
|
||||
if _has_option('proxy'):
|
||||
proxy = _config_value('proxy')
|
||||
|
||||
return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags,
|
||||
enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert,
|
||||
cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password,
|
||||
connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic),
|
||||
profilers=profilers, proxy=proxy, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def _from_env_properties(cls, debug=None, enable_gzip=False, **kwargs):
|
||||
url = os.getenv('INFLUXDB_V2_URL', "http://localhost:8086")
|
||||
token = os.getenv('INFLUXDB_V2_TOKEN', "my-token")
|
||||
timeout = os.getenv('INFLUXDB_V2_TIMEOUT', "10000")
|
||||
org = os.getenv('INFLUXDB_V2_ORG', "my-org")
|
||||
verify_ssl = os.getenv('INFLUXDB_V2_VERIFY_SSL', "True")
|
||||
ssl_ca_cert = os.getenv('INFLUXDB_V2_SSL_CA_CERT', None)
|
||||
cert_file = os.getenv('INFLUXDB_V2_CERT_FILE', None)
|
||||
cert_key_file = os.getenv('INFLUXDB_V2_CERT_KEY_FILE', None)
|
||||
cert_key_password = os.getenv('INFLUXDB_V2_CERT_KEY_PASSWORD', None)
|
||||
connection_pool_maxsize = os.getenv('INFLUXDB_V2_CONNECTION_POOL_MAXSIZE', None)
|
||||
auth_basic = os.getenv('INFLUXDB_V2_AUTH_BASIC', "False")
|
||||
|
||||
prof = os.getenv("INFLUXDB_V2_PROFILERS", None)
|
||||
profilers = None
|
||||
if prof is not None:
|
||||
profilers = [x.strip() for x in prof.split(',')]
|
||||
|
||||
default_tags = dict()
|
||||
|
||||
for key, value in os.environ.items():
|
||||
if key.startswith("INFLUXDB_V2_TAG_"):
|
||||
default_tags[key[16:].lower()] = value
|
||||
|
||||
return cls(url, token, debug=debug, timeout=_to_int(timeout), org=org, default_tags=default_tags,
|
||||
enable_gzip=enable_gzip, verify_ssl=_to_bool(verify_ssl), ssl_ca_cert=ssl_ca_cert,
|
||||
cert_file=cert_file, cert_key_file=cert_key_file, cert_key_password=cert_key_password,
|
||||
connection_pool_maxsize=_to_int(connection_pool_maxsize), auth_basic=_to_bool(auth_basic),
|
||||
profilers=profilers, **kwargs)
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class _BaseQueryApi(object):
|
||||
default_dialect = Dialect(header=True, delimiter=",", comment_prefix="#",
|
||||
annotations=["datatype", "group", "default"], date_time_format="RFC3339")
|
||||
|
||||
def __init__(self, influxdb_client, query_options=None):
|
||||
from influxdb_client.client.query_api import QueryOptions
|
||||
self._query_options = QueryOptions() if query_options is None else query_options
|
||||
self._influxdb_client = influxdb_client
|
||||
self._query_api = QueryService(influxdb_client.api_client)
|
||||
|
||||
"""Base implementation for Queryable API."""
|
||||
|
||||
def _to_tables(self, response, query_options=None, response_metadata_mode:
|
||||
FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> TableList:
|
||||
"""
|
||||
Parse HTTP response to TableList.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`.
|
||||
"""
|
||||
_parser = self._to_tables_parser(response, query_options, response_metadata_mode)
|
||||
list(_parser.generator())
|
||||
return _parser.table_list()
|
||||
|
||||
async def _to_tables_async(self, response, query_options=None, response_metadata_mode:
|
||||
FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> TableList:
|
||||
"""
|
||||
Parse HTTP response to TableList.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`.
|
||||
"""
|
||||
async with self._to_tables_parser(response, query_options, response_metadata_mode) as parser:
|
||||
async for _ in parser.generator_async():
|
||||
pass
|
||||
return parser.table_list()
|
||||
|
||||
def _to_csv(self, response: HTTPResponse) -> CSVIterator:
|
||||
"""Parse HTTP response to CSV."""
|
||||
return CSVIterator(response)
|
||||
|
||||
def _to_flux_record_stream(self, response, query_options=None,
|
||||
response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> \
|
||||
Generator[FluxRecord, Any, None]:
|
||||
"""
|
||||
Parse HTTP response to FluxRecord stream.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`.
|
||||
"""
|
||||
_parser = self._to_flux_record_stream_parser(query_options, response, response_metadata_mode)
|
||||
return _parser.generator()
|
||||
|
||||
async def _to_flux_record_stream_async(self, response, query_options=None, response_metadata_mode:
|
||||
FluxResponseMetadataMode = FluxResponseMetadataMode.full) -> \
|
||||
AsyncGenerator['FluxRecord', None]:
|
||||
"""
|
||||
Parse HTTP response to FluxRecord stream.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`.
|
||||
"""
|
||||
_parser = self._to_flux_record_stream_parser(query_options, response, response_metadata_mode)
|
||||
return (await _parser.__aenter__()).generator_async()
|
||||
|
||||
def _to_data_frame_stream(self, data_frame_index, response, query_options=None,
|
||||
response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full,
|
||||
use_extension_dtypes=False):
|
||||
"""
|
||||
Parse HTTP response to DataFrame stream.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `urllib3.response.HTTPResponse`.
|
||||
"""
|
||||
_parser = self._to_data_frame_stream_parser(data_frame_index, query_options, response, response_metadata_mode,
|
||||
use_extension_dtypes)
|
||||
return _parser.generator()
|
||||
|
||||
async def _to_data_frame_stream_async(self, data_frame_index, response, query_options=None, response_metadata_mode:
|
||||
FluxResponseMetadataMode = FluxResponseMetadataMode.full,
|
||||
use_extension_dtypes=False):
|
||||
"""
|
||||
Parse HTTP response to DataFrame stream.
|
||||
|
||||
:param response: HTTP response from an HTTP client. Expected type: `aiohttp.client_reqrep.ClientResponse`.
|
||||
"""
|
||||
_parser = self._to_data_frame_stream_parser(data_frame_index, query_options, response, response_metadata_mode,
|
||||
use_extension_dtypes)
|
||||
return (await _parser.__aenter__()).generator_async()
|
||||
|
||||
def _to_tables_parser(self, response, query_options, response_metadata_mode):
|
||||
return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.tables,
|
||||
query_options=query_options, response_metadata_mode=response_metadata_mode)
|
||||
|
||||
def _to_flux_record_stream_parser(self, query_options, response, response_metadata_mode):
|
||||
return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.stream,
|
||||
query_options=query_options, response_metadata_mode=response_metadata_mode)
|
||||
|
||||
def _to_data_frame_stream_parser(self, data_frame_index, query_options, response, response_metadata_mode,
|
||||
use_extension_dtypes):
|
||||
return FluxCsvParser(response=response, serialization_mode=FluxSerializationMode.dataFrame,
|
||||
data_frame_index=data_frame_index, query_options=query_options,
|
||||
response_metadata_mode=response_metadata_mode,
|
||||
use_extension_dtypes=use_extension_dtypes)
|
||||
|
||||
def _to_data_frames(self, _generator):
|
||||
"""Parse stream of DataFrames into expected type."""
|
||||
from ..extras import pd
|
||||
if isinstance(_generator, list):
|
||||
_dataFrames = _generator
|
||||
else:
|
||||
_dataFrames = list(_generator)
|
||||
|
||||
if len(_dataFrames) == 0:
|
||||
return pd.DataFrame(columns=[], index=None)
|
||||
elif len(_dataFrames) == 1:
|
||||
return _dataFrames[0]
|
||||
else:
|
||||
return _dataFrames
|
||||
|
||||
def _org_param(self, org):
|
||||
return get_org_query_param(org=org, client=self._influxdb_client)
|
||||
|
||||
def _get_query_options(self):
|
||||
if self._query_options and self._query_options.profilers:
|
||||
return self._query_options
|
||||
elif self._influxdb_client.profilers:
|
||||
from influxdb_client.client.query_api import QueryOptions
|
||||
return QueryOptions(profilers=self._influxdb_client.profilers)
|
||||
|
||||
def _create_query(self, query, dialect=default_dialect, params: dict = None, **kwargs):
|
||||
query_options = self._get_query_options()
|
||||
profilers = query_options.profilers if query_options is not None else None
|
||||
q = Query(query=query, dialect=dialect, extern=_BaseQueryApi._build_flux_ast(params, profilers))
|
||||
|
||||
if profilers:
|
||||
print("\n===============")
|
||||
print("Profiler: query")
|
||||
print("===============")
|
||||
print(query)
|
||||
|
||||
if kwargs.get('dataframe_query', False):
|
||||
MissingPivotFunction.print_warning(query)
|
||||
|
||||
return q
|
||||
|
||||
@staticmethod
|
||||
def _params_to_extern_ast(params: dict) -> List['OptionStatement']:
|
||||
|
||||
statements = []
|
||||
for key, value in params.items():
|
||||
expression = _BaseQueryApi._parm_to_extern_ast(value)
|
||||
if expression is None:
|
||||
continue
|
||||
|
||||
statements.append(OptionStatement("OptionStatement",
|
||||
VariableAssignment("VariableAssignment", Identifier("Identifier", key),
|
||||
expression)))
|
||||
return statements
|
||||
|
||||
@staticmethod
|
||||
def _parm_to_extern_ast(value) -> Union[Expression, None]:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, bool):
|
||||
return BooleanLiteral("BooleanLiteral", value)
|
||||
elif isinstance(value, int):
|
||||
return IntegerLiteral("IntegerLiteral", str(value))
|
||||
elif isinstance(value, float):
|
||||
return FloatLiteral("FloatLiteral", value)
|
||||
elif isinstance(value, datetime):
|
||||
value = get_date_helper().to_utc(value)
|
||||
nanoseconds = getattr(value, 'nanosecond', 0)
|
||||
fraction = f'{(value.microsecond * 1000 + nanoseconds):09d}'
|
||||
return DateTimeLiteral("DateTimeLiteral", value.strftime('%Y-%m-%dT%H:%M:%S.') + fraction + 'Z')
|
||||
elif isinstance(value, timedelta):
|
||||
_micro_delta = int(value / timedelta(microseconds=1))
|
||||
if _micro_delta < 0:
|
||||
return UnaryExpression("UnaryExpression", argument=DurationLiteral("DurationLiteral", [
|
||||
Duration(magnitude=-_micro_delta, unit="us")]), operator="-")
|
||||
else:
|
||||
return DurationLiteral("DurationLiteral", [Duration(magnitude=_micro_delta, unit="us")])
|
||||
elif isinstance(value, str):
|
||||
return StringLiteral("StringLiteral", str(value))
|
||||
elif isinstance(value, Iterable):
|
||||
return ArrayExpression("ArrayExpression",
|
||||
elements=list(map(lambda it: _BaseQueryApi._parm_to_extern_ast(it), value)))
|
||||
else:
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _build_flux_ast(params: dict = None, profilers: List[str] = None):
|
||||
|
||||
imports = []
|
||||
body = []
|
||||
|
||||
if profilers is not None and len(profilers) > 0:
|
||||
imports.append(ImportDeclaration(
|
||||
"ImportDeclaration",
|
||||
path=StringLiteral("StringLiteral", "profiler")))
|
||||
|
||||
elements = []
|
||||
for profiler in profilers:
|
||||
elements.append(StringLiteral("StringLiteral", value=profiler))
|
||||
|
||||
member = MemberExpression(
|
||||
"MemberExpression",
|
||||
object=Identifier("Identifier", "profiler"),
|
||||
_property=Identifier("Identifier", "enabledProfilers"))
|
||||
|
||||
prof = OptionStatement(
|
||||
"OptionStatement",
|
||||
assignment=MemberAssignment(
|
||||
"MemberAssignment",
|
||||
member=member,
|
||||
init=ArrayExpression(
|
||||
"ArrayExpression",
|
||||
elements=elements)))
|
||||
|
||||
body.append(prof)
|
||||
|
||||
if params is not None:
|
||||
body.extend(_BaseQueryApi._params_to_extern_ast(params))
|
||||
|
||||
return File(package=None, name=None, type=None, imports=imports, body=body)
|
||||
|
||||
|
||||
class _BaseWriteApi(object):
|
||||
def __init__(self, influxdb_client, point_settings=None):
|
||||
self._influxdb_client = influxdb_client
|
||||
self._point_settings = point_settings
|
||||
self._write_service = WriteService(influxdb_client.api_client)
|
||||
if influxdb_client.default_tags:
|
||||
for key, value in influxdb_client.default_tags.items():
|
||||
self._point_settings.add_default_tag(key, value)
|
||||
|
||||
def _append_default_tag(self, key, val, record):
|
||||
from influxdb_client import Point
|
||||
if isinstance(record, bytes) or isinstance(record, str):
|
||||
pass
|
||||
elif isinstance(record, Point):
|
||||
record.tag(key, val)
|
||||
elif isinstance(record, dict):
|
||||
record.setdefault("tags", {})
|
||||
record.get("tags")[key] = val
|
||||
elif isinstance(record, Iterable):
|
||||
for item in record:
|
||||
self._append_default_tag(key, val, item)
|
||||
|
||||
def _append_default_tags(self, record):
|
||||
if self._point_settings.defaultTags and record is not None:
|
||||
for key, val in self._point_settings.defaultTags.items():
|
||||
self._append_default_tag(key, val, record)
|
||||
|
||||
def _serialize(self, record, write_precision, payload, **kwargs):
|
||||
from influxdb_client import Point
|
||||
if isinstance(record, bytes):
|
||||
payload[write_precision].append(record)
|
||||
|
||||
elif isinstance(record, str):
|
||||
self._serialize(record.encode(_UTF_8_encoding), write_precision, payload, **kwargs)
|
||||
|
||||
elif isinstance(record, Point):
|
||||
precision_from_point = kwargs.get('precision_from_point', True)
|
||||
precision = record.write_precision if precision_from_point else write_precision
|
||||
self._serialize(record.to_line_protocol(precision=precision), precision, payload, **kwargs)
|
||||
|
||||
elif isinstance(record, dict):
|
||||
self._serialize(Point.from_dict(record, write_precision=write_precision, **kwargs),
|
||||
write_precision, payload, **kwargs)
|
||||
elif 'DataFrame' in type(record).__name__:
|
||||
serializer = DataframeSerializer(record, self._point_settings, write_precision, **kwargs)
|
||||
self._serialize(serializer.serialize(), write_precision, payload, **kwargs)
|
||||
elif hasattr(record, "_asdict"):
|
||||
# noinspection PyProtectedMember
|
||||
self._serialize(record._asdict(), write_precision, payload, **kwargs)
|
||||
elif _HAS_DATACLASS and dataclasses.is_dataclass(record):
|
||||
self._serialize(dataclasses.asdict(record), write_precision, payload, **kwargs)
|
||||
elif isinstance(record, Iterable):
|
||||
for item in record:
|
||||
self._serialize(item, write_precision, payload, **kwargs)
|
||||
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
class _BaseDeleteApi(object):
|
||||
def __init__(self, influxdb_client):
|
||||
self._influxdb_client = influxdb_client
|
||||
self._service = DeleteService(influxdb_client.api_client)
|
||||
|
||||
def _prepare_predicate_request(self, start, stop, predicate):
|
||||
date_helper = get_date_helper()
|
||||
if isinstance(start, datetime):
|
||||
start = date_helper.to_utc(start)
|
||||
if isinstance(stop, datetime):
|
||||
stop = date_helper.to_utc(stop)
|
||||
predicate_request = DeletePredicateRequest(start=start, stop=stop, predicate=predicate)
|
||||
return predicate_request
|
||||
|
||||
|
||||
class _Configuration(Configuration):
|
||||
def __init__(self):
|
||||
Configuration.__init__(self)
|
||||
self.enable_gzip = False
|
||||
self.username = None
|
||||
self.password = None
|
||||
|
||||
def update_request_header_params(self, path: str, params: dict):
|
||||
super().update_request_header_params(path, params)
|
||||
if self.enable_gzip:
|
||||
# GZIP Request
|
||||
if path == '/api/v2/write':
|
||||
params["Content-Encoding"] = "gzip"
|
||||
params["Accept-Encoding"] = "identity"
|
||||
pass
|
||||
# GZIP Response
|
||||
if path == '/api/v2/query':
|
||||
# params["Content-Encoding"] = "gzip"
|
||||
params["Accept-Encoding"] = "gzip"
|
||||
pass
|
||||
pass
|
||||
pass
|
||||
|
||||
def update_request_body(self, path: str, body):
|
||||
_body = super().update_request_body(path, body)
|
||||
if self.enable_gzip:
|
||||
# GZIP Request
|
||||
if path == '/api/v2/write':
|
||||
import gzip
|
||||
if isinstance(_body, bytes):
|
||||
return gzip.compress(data=_body)
|
||||
else:
|
||||
return gzip.compress(bytes(_body, _UTF_8_encoding))
|
||||
|
||||
return _body
|
||||
|
||||
|
||||
def _to_bool(bool_value):
|
||||
return str(bool_value).lower() in ("yes", "true")
|
||||
|
||||
|
||||
def _to_int(int_value):
|
||||
return int(int_value) if int_value is not None else None
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
|
||||
class _Page:
|
||||
def __init__(self, values, has_next, next_after):
|
||||
self.has_next = has_next
|
||||
self.values = values
|
||||
self.next_after = next_after
|
||||
|
||||
@staticmethod
|
||||
def empty():
|
||||
return _Page([], False, None)
|
||||
|
||||
@staticmethod
|
||||
def initial(after):
|
||||
return _Page([], True, after)
|
||||
|
||||
|
||||
class _PageIterator:
|
||||
def __init__(self, page: _Page, get_next_page):
|
||||
self.page = page
|
||||
self.get_next_page = get_next_page
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if not self.page.values:
|
||||
if self.page.has_next:
|
||||
self.page = self.get_next_page(self.page)
|
||||
if not self.page.values:
|
||||
raise StopIteration
|
||||
return self.page.values.pop(0)
|
||||
|
||||
|
||||
class _Paginated:
|
||||
def __init__(self, paginated_getter, pluck_page_resources_from_response):
|
||||
self.paginated_getter = paginated_getter
|
||||
self.pluck_page_resources_from_response = pluck_page_resources_from_response
|
||||
|
||||
def find_iter(self, **kwargs):
|
||||
"""Iterate over resources with pagination.
|
||||
|
||||
:key str org: The organization name.
|
||||
:key str org_id: The organization ID.
|
||||
:key str after: The last resource ID from which to seek from (but not including).
|
||||
:key int limit: the maximum number of items per page
|
||||
:return: resources iterator
|
||||
"""
|
||||
|
||||
def get_next_page(page: _Page):
|
||||
return self._find_next_page(page, **kwargs)
|
||||
|
||||
return iter(_PageIterator(_Page.initial(kwargs.get('after')), get_next_page))
|
||||
|
||||
def _find_next_page(self, page: _Page, **kwargs):
|
||||
if not page.has_next:
|
||||
return _Page.empty()
|
||||
|
||||
kw_args = {**kwargs, 'after': page.next_after} if page.next_after is not None else kwargs
|
||||
response = self.paginated_getter(**kw_args)
|
||||
|
||||
resources = self.pluck_page_resources_from_response(response)
|
||||
has_next = response.links.next is not None
|
||||
last_id = resources[-1].id if resources else None
|
||||
|
||||
return _Page(resources, has_next, last_id)
|
||||
@@ -1,136 +0,0 @@
|
||||
"""Authorization is about managing the security of your InfluxDB instance."""
|
||||
|
||||
from influxdb_client import Authorization, AuthorizationsService, User, Organization
|
||||
|
||||
|
||||
class AuthorizationsApi(object):
|
||||
"""Implementation for '/api/v2/authorizations' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._authorizations_service = AuthorizationsService(influxdb_client.api_client)
|
||||
|
||||
def create_authorization(self, org_id: str = None, permissions: list = None,
|
||||
authorization: Authorization = None) -> Authorization:
|
||||
"""
|
||||
Create an authorization.
|
||||
|
||||
:type permissions: list of Permission
|
||||
:param org_id: organization id
|
||||
:param permissions: list of permissions
|
||||
:type authorization: authorization object
|
||||
|
||||
"""
|
||||
if authorization is not None:
|
||||
if not isinstance(authorization, Authorization):
|
||||
raise TypeError(f"Attempt to use non-Authorization value for authorization: {authorization}")
|
||||
return self._authorizations_service.post_authorizations(authorization_post_request=authorization)
|
||||
|
||||
# if org_id is not None and permissions is not None:
|
||||
authorization = Authorization(org_id=org_id, permissions=permissions)
|
||||
return self._authorizations_service.post_authorizations(authorization_post_request=authorization)
|
||||
|
||||
def find_authorization_by_id(self, auth_id: str) -> Authorization:
|
||||
"""
|
||||
Find authorization by id.
|
||||
|
||||
:param auth_id: authorization id
|
||||
:return: Authorization
|
||||
"""
|
||||
return self._authorizations_service.get_authorizations_id(auth_id=auth_id)
|
||||
|
||||
def find_authorizations(self, **kwargs):
|
||||
"""
|
||||
Get a list of all authorizations.
|
||||
|
||||
:key str user_id: filter authorizations belonging to a user id
|
||||
:key str user: filter authorizations belonging to a user name
|
||||
:key str org_id: filter authorizations belonging to a org id
|
||||
:key str org: filter authorizations belonging to a org name
|
||||
:return: Authorizations
|
||||
"""
|
||||
authorizations = self._authorizations_service.get_authorizations(**kwargs)
|
||||
|
||||
return authorizations.authorizations
|
||||
|
||||
def find_authorizations_by_user(self, user: User):
|
||||
"""
|
||||
Find authorization by User.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
return self.find_authorizations(user_id=user.id)
|
||||
|
||||
def find_authorizations_by_user_id(self, user_id: str):
|
||||
"""
|
||||
Find authorization by user id.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
return self.find_authorizations(user_id=user_id)
|
||||
|
||||
def find_authorizations_by_user_name(self, user_name: str):
|
||||
"""
|
||||
Find authorization by user name.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
return self.find_authorizations(user=user_name)
|
||||
|
||||
def find_authorizations_by_org(self, org: Organization):
|
||||
"""
|
||||
Find authorization by user name.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
if isinstance(org, Organization):
|
||||
return self.find_authorizations(org_id=org.id)
|
||||
|
||||
def find_authorizations_by_org_name(self, org_name: str):
|
||||
"""
|
||||
Find authorization by org name.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
return self.find_authorizations(org=org_name)
|
||||
|
||||
def find_authorizations_by_org_id(self, org_id: str):
|
||||
"""
|
||||
Find authorization by org id.
|
||||
|
||||
:return: Authorization list
|
||||
"""
|
||||
return self.find_authorizations(org_id=org_id)
|
||||
|
||||
def update_authorization(self, auth):
|
||||
"""
|
||||
Update authorization object.
|
||||
|
||||
:param auth:
|
||||
:return:
|
||||
"""
|
||||
return self._authorizations_service.patch_authorizations_id(auth_id=auth.id, authorization_update_request=auth)
|
||||
|
||||
def clone_authorization(self, auth) -> Authorization:
|
||||
"""Clone an authorization."""
|
||||
if isinstance(auth, Authorization):
|
||||
cloned = Authorization(org_id=auth.org_id, permissions=auth.permissions)
|
||||
# cloned.description = auth.description
|
||||
# cloned.status = auth.status
|
||||
return self.create_authorization(authorization=cloned)
|
||||
|
||||
if isinstance(auth, str):
|
||||
authorization = self.find_authorization_by_id(auth)
|
||||
return self.clone_authorization(auth=authorization)
|
||||
|
||||
raise ValueError("Invalid argument")
|
||||
|
||||
def delete_authorization(self, auth):
|
||||
"""Delete a authorization."""
|
||||
if isinstance(auth, Authorization):
|
||||
return self._authorizations_service.delete_authorizations_id(auth_id=auth.id)
|
||||
|
||||
if isinstance(auth, str):
|
||||
return self._authorizations_service.delete_authorizations_id(auth_id=auth)
|
||||
raise ValueError("Invalid argument")
|
||||
@@ -1,132 +0,0 @@
|
||||
"""
|
||||
A bucket is a named location where time series data is stored.
|
||||
|
||||
All buckets have a retention policy, a duration of time that each data point persists.
|
||||
A bucket belongs to an organization.
|
||||
"""
|
||||
import warnings
|
||||
|
||||
from influxdb_client import BucketsService, Bucket, PostBucketRequest, PatchBucketRequest
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
from influxdb_client.client._pages import _Paginated
|
||||
|
||||
|
||||
class BucketsApi(object):
|
||||
"""Implementation for '/api/v2/buckets' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._buckets_service = BucketsService(influxdb_client.api_client)
|
||||
|
||||
def create_bucket(self, bucket=None, bucket_name=None, org_id=None, retention_rules=None,
|
||||
description=None, org=None) -> Bucket:
|
||||
"""Create a bucket.
|
||||
|
||||
:param Bucket|PostBucketRequest bucket: bucket to create
|
||||
:param bucket_name: bucket name
|
||||
:param description: bucket description
|
||||
:param org_id: org_id
|
||||
:param bucket_name: bucket name
|
||||
:param retention_rules: retention rules array or single BucketRetentionRules
|
||||
:param str, Organization org: specifies the organization for create the bucket;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:return: Bucket
|
||||
If the method is called asynchronously,
|
||||
returns the request thread.
|
||||
"""
|
||||
if retention_rules is None:
|
||||
retention_rules = []
|
||||
|
||||
rules = []
|
||||
|
||||
if isinstance(retention_rules, list):
|
||||
rules.extend(retention_rules)
|
||||
else:
|
||||
rules.append(retention_rules)
|
||||
|
||||
if org_id is not None:
|
||||
warnings.warn("org_id is deprecated; use org", DeprecationWarning)
|
||||
|
||||
if bucket is None:
|
||||
bucket = PostBucketRequest(name=bucket_name,
|
||||
retention_rules=rules,
|
||||
description=description,
|
||||
org_id=get_org_query_param(org=(org_id if org is None else org),
|
||||
client=self._influxdb_client,
|
||||
required_id=True))
|
||||
|
||||
return self._buckets_service.post_buckets(post_bucket_request=bucket)
|
||||
|
||||
def update_bucket(self, bucket: Bucket) -> Bucket:
|
||||
"""Update a bucket.
|
||||
|
||||
:param bucket: Bucket update to apply (required)
|
||||
:return: Bucket
|
||||
"""
|
||||
request = PatchBucketRequest(name=bucket.name,
|
||||
description=bucket.description,
|
||||
retention_rules=bucket.retention_rules)
|
||||
|
||||
return self._buckets_service.patch_buckets_id(bucket_id=bucket.id, patch_bucket_request=request)
|
||||
|
||||
def delete_bucket(self, bucket):
|
||||
"""Delete a bucket.
|
||||
|
||||
:param bucket: bucket id or Bucket
|
||||
:return: Bucket
|
||||
"""
|
||||
if isinstance(bucket, Bucket):
|
||||
bucket_id = bucket.id
|
||||
else:
|
||||
bucket_id = bucket
|
||||
|
||||
return self._buckets_service.delete_buckets_id(bucket_id=bucket_id)
|
||||
|
||||
def find_bucket_by_id(self, id):
|
||||
"""Find bucket by ID.
|
||||
|
||||
:param id:
|
||||
:return:
|
||||
"""
|
||||
return self._buckets_service.get_buckets_id(id)
|
||||
|
||||
def find_bucket_by_name(self, bucket_name):
|
||||
"""Find bucket by name.
|
||||
|
||||
:param bucket_name: bucket name
|
||||
:return: Bucket
|
||||
"""
|
||||
buckets = self._buckets_service.get_buckets(name=bucket_name)
|
||||
|
||||
if len(buckets.buckets) > 0:
|
||||
return buckets.buckets[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def find_buckets(self, **kwargs):
|
||||
"""List buckets.
|
||||
|
||||
:key int offset: Offset for pagination
|
||||
:key int limit: Limit for pagination
|
||||
:key str after: The last resource ID from which to seek from (but not including).
|
||||
This is to be used instead of `offset`.
|
||||
:key str org: The organization name.
|
||||
:key str org_id: The organization ID.
|
||||
:key str name: Only returns buckets with a specific name.
|
||||
:return: Buckets
|
||||
"""
|
||||
return self._buckets_service.get_buckets(**kwargs)
|
||||
|
||||
def find_buckets_iter(self, **kwargs):
|
||||
"""Iterate over all buckets with pagination.
|
||||
|
||||
:key str name: Only returns buckets with the specified name
|
||||
:key str org: The organization name.
|
||||
:key str org_id: The organization ID.
|
||||
:key str after: The last resource ID from which to seek from (but not including).
|
||||
:key int limit: the maximum number of buckets in one page
|
||||
:return: Buckets iterator
|
||||
"""
|
||||
return _Paginated(self._buckets_service.get_buckets, lambda response: response.buckets).find_iter(**kwargs)
|
||||
@@ -1,35 +0,0 @@
|
||||
"""Delete time series data from InfluxDB."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
from influxdb_client import Organization
|
||||
from influxdb_client.client._base import _BaseDeleteApi
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
|
||||
|
||||
class DeleteApi(_BaseDeleteApi):
|
||||
"""Implementation for '/api/v2/delete' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
super().__init__(influxdb_client)
|
||||
|
||||
def delete(self, start: Union[str, datetime], stop: Union[str, datetime], predicate: str, bucket: str,
|
||||
org: Union[str, Organization, None] = None) -> None:
|
||||
"""
|
||||
Delete Time series data from InfluxDB.
|
||||
|
||||
:param str, datetime.datetime start: start time
|
||||
:param str, datetime.datetime stop: stop time
|
||||
:param str predicate: predicate
|
||||
:param str bucket: bucket id or name from which data will be deleted
|
||||
:param str, Organization org: specifies the organization to delete data from.
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:return:
|
||||
"""
|
||||
predicate_request = self._prepare_predicate_request(start, stop, predicate)
|
||||
org_param = get_org_query_param(org=org, client=self._influxdb_client, required_id=False)
|
||||
|
||||
return self._service.post_delete(delete_predicate_request=predicate_request, bucket=bucket, org=org_param)
|
||||
@@ -1,37 +0,0 @@
|
||||
"""Delete time series data from InfluxDB."""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
from influxdb_client import Organization
|
||||
from influxdb_client.client._base import _BaseDeleteApi
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
|
||||
|
||||
class DeleteApiAsync(_BaseDeleteApi):
|
||||
"""Async implementation for '/api/v2/delete' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
super().__init__(influxdb_client)
|
||||
|
||||
async def delete(self, start: Union[str, datetime], stop: Union[str, datetime], predicate: str, bucket: str,
|
||||
org: Union[str, Organization, None] = None) -> bool:
|
||||
"""
|
||||
Delete Time series data from InfluxDB.
|
||||
|
||||
:param str, datetime.datetime start: start time
|
||||
:param str, datetime.datetime stop: stop time
|
||||
:param str predicate: predicate
|
||||
:param str bucket: bucket id or name from which data will be deleted
|
||||
:param str, Organization org: specifies the organization to delete data from.
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:return: ``True`` for successfully deleted data, otherwise raise an exception
|
||||
"""
|
||||
predicate_request = self._prepare_predicate_request(start, stop, predicate)
|
||||
org_param = get_org_query_param(org=org, client=self._influxdb_client, required_id=False)
|
||||
|
||||
response = await self._service.post_delete_async(delete_predicate_request=predicate_request, bucket=bucket,
|
||||
org=org_param, _return_http_data_only=False)
|
||||
return response[1] == 204
|
||||
@@ -1,47 +0,0 @@
|
||||
"""Exceptions utils for InfluxDB."""
|
||||
|
||||
import logging
|
||||
|
||||
from urllib3 import HTTPResponse
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.exceptions')
|
||||
|
||||
|
||||
class InfluxDBError(Exception):
|
||||
"""Raised when a server error occurs."""
|
||||
|
||||
def __init__(self, response: HTTPResponse = None, message: str = None):
|
||||
"""Initialize the InfluxDBError handler."""
|
||||
if response is not None:
|
||||
self.response = response
|
||||
self.message = self._get_message(response)
|
||||
if isinstance(response, HTTPResponse): # response is HTTPResponse
|
||||
self.headers = response.headers
|
||||
self.retry_after = response.headers.get('Retry-After')
|
||||
else: # response is RESTResponse
|
||||
self.headers = response.getheaders()
|
||||
self.retry_after = response.getheader('Retry-After')
|
||||
else:
|
||||
self.response = None
|
||||
self.message = message or 'no response'
|
||||
self.retry_after = None
|
||||
super().__init__(self.message)
|
||||
|
||||
def _get_message(self, response):
|
||||
# Body
|
||||
if response.data:
|
||||
import json
|
||||
try:
|
||||
return json.loads(response.data)["message"]
|
||||
except Exception as e:
|
||||
logging.debug(f"Cannot parse error response to JSON: {response.data}, {e}")
|
||||
return response.data
|
||||
|
||||
# Header
|
||||
for header_key in ["X-Platform-Error-Code", "X-Influx-Error", "X-InfluxDb-Error"]:
|
||||
header_value = response.getheader(header_key)
|
||||
if header_value is not None:
|
||||
return header_value
|
||||
|
||||
# Http Status
|
||||
return response.reason
|
||||
@@ -1,408 +0,0 @@
|
||||
"""Parsing response from InfluxDB to FluxStructures or DataFrame."""
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import csv as csv_parser
|
||||
import warnings
|
||||
from enum import Enum
|
||||
from typing import List
|
||||
|
||||
from influxdb_client.client.flux_table import FluxTable, FluxColumn, FluxRecord, TableList
|
||||
from influxdb_client.client.util.date_utils import get_date_helper
|
||||
from influxdb_client.rest import _UTF_8_encoding
|
||||
|
||||
ANNOTATION_DEFAULT = "#default"
|
||||
ANNOTATION_GROUP = "#group"
|
||||
ANNOTATION_DATATYPE = "#datatype"
|
||||
ANNOTATIONS = [ANNOTATION_DEFAULT, ANNOTATION_GROUP, ANNOTATION_DATATYPE]
|
||||
|
||||
|
||||
class FluxQueryException(Exception):
|
||||
"""The exception from InfluxDB."""
|
||||
|
||||
def __init__(self, message, reference) -> None:
|
||||
"""Initialize defaults."""
|
||||
self.message = message
|
||||
self.reference = reference
|
||||
|
||||
|
||||
class FluxCsvParserException(Exception):
|
||||
"""The exception for not parsable data."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FluxSerializationMode(Enum):
|
||||
"""The type how we want to serialize data."""
|
||||
|
||||
tables = 1
|
||||
stream = 2
|
||||
dataFrame = 3
|
||||
|
||||
|
||||
class FluxResponseMetadataMode(Enum):
|
||||
"""The configuration for expected amount of metadata response from InfluxDB."""
|
||||
|
||||
full = 1
|
||||
# useful for Invokable scripts
|
||||
only_names = 2
|
||||
|
||||
|
||||
class _FluxCsvParserMetadata(object):
|
||||
def __init__(self):
|
||||
self.table_index = 0
|
||||
self.table_id = -1
|
||||
self.start_new_table = False
|
||||
self.table = None
|
||||
self.groups = []
|
||||
self.parsing_state_error = False
|
||||
|
||||
|
||||
class FluxCsvParser(object):
|
||||
"""Parse to processing response from InfluxDB to FluxStructures or DataFrame."""
|
||||
|
||||
def __init__(self, response, serialization_mode: FluxSerializationMode,
|
||||
data_frame_index: List[str] = None, query_options=None,
|
||||
response_metadata_mode: FluxResponseMetadataMode = FluxResponseMetadataMode.full,
|
||||
use_extension_dtypes=False) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param response: HTTP response from a HTTP client.
|
||||
Acceptable types: `urllib3.response.HTTPResponse`, `aiohttp.client_reqrep.ClientResponse`.
|
||||
"""
|
||||
self._response = response
|
||||
self.tables = TableList()
|
||||
self._serialization_mode = serialization_mode
|
||||
self._response_metadata_mode = response_metadata_mode
|
||||
self._use_extension_dtypes = use_extension_dtypes
|
||||
self._data_frame_index = data_frame_index
|
||||
self._data_frame_values = []
|
||||
self._profilers = query_options.profilers if query_options is not None else None
|
||||
self._profiler_callback = query_options.profiler_callback if query_options is not None else None
|
||||
self._async_mode = True if 'ClientResponse' in type(response).__name__ else False
|
||||
|
||||
def _close(self):
|
||||
self._response.close()
|
||||
|
||||
def __enter__(self):
|
||||
"""Initialize CSV reader."""
|
||||
# response can be exhausted by logger, so we have to use data that has already been read
|
||||
if hasattr(self._response, 'closed') and self._response.closed:
|
||||
from io import StringIO
|
||||
self._reader = csv_parser.reader(StringIO(self._response.data.decode(_UTF_8_encoding)))
|
||||
else:
|
||||
self._reader = csv_parser.reader(codecs.iterdecode(self._response, _UTF_8_encoding))
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Close HTTP response."""
|
||||
self._close()
|
||||
|
||||
async def __aenter__(self) -> 'FluxCsvParser':
|
||||
"""Initialize CSV reader."""
|
||||
from aiocsv import AsyncReader
|
||||
self._reader = AsyncReader(_StreamReaderToWithAsyncRead(self._response.content))
|
||||
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
"""Shutdown the client."""
|
||||
self.__exit__(exc_type, exc_val, exc_tb)
|
||||
|
||||
def generator(self):
|
||||
"""Return Python generator."""
|
||||
with self as parser:
|
||||
for val in parser._parse_flux_response():
|
||||
yield val
|
||||
|
||||
def generator_async(self):
|
||||
"""Return Python async-generator."""
|
||||
return self._parse_flux_response_async()
|
||||
|
||||
def _parse_flux_response(self):
|
||||
metadata = _FluxCsvParserMetadata()
|
||||
|
||||
for csv in self._reader:
|
||||
for val in self._parse_flux_response_row(metadata, csv):
|
||||
yield val
|
||||
|
||||
# Return latest DataFrame
|
||||
if (self._serialization_mode is FluxSerializationMode.dataFrame) & hasattr(self, '_data_frame'):
|
||||
df = self._prepare_data_frame()
|
||||
if not self._is_profiler_table(metadata.table):
|
||||
yield df
|
||||
|
||||
async def _parse_flux_response_async(self):
|
||||
metadata = _FluxCsvParserMetadata()
|
||||
|
||||
try:
|
||||
async for csv in self._reader:
|
||||
for val in self._parse_flux_response_row(metadata, csv):
|
||||
yield val
|
||||
|
||||
# Return latest DataFrame
|
||||
if (self._serialization_mode is FluxSerializationMode.dataFrame) & hasattr(self, '_data_frame'):
|
||||
df = self._prepare_data_frame()
|
||||
if not self._is_profiler_table(metadata.table):
|
||||
yield df
|
||||
except BaseException as e:
|
||||
e_type = type(e).__name__
|
||||
if "CancelledError" in e_type or "TimeoutError" in e_type:
|
||||
e.add_note("Stream cancelled during read. Recommended: Check Influxdb client `timeout` setting.")
|
||||
raise
|
||||
finally:
|
||||
self._close()
|
||||
|
||||
def _parse_flux_response_row(self, metadata, csv):
|
||||
if len(csv) < 1:
|
||||
# Skip empty line in results (new line is used as a delimiter between tables or table and error)
|
||||
pass
|
||||
|
||||
elif "error" == csv[1] and "reference" == csv[2]:
|
||||
metadata.parsing_state_error = True
|
||||
|
||||
else:
|
||||
# Throw InfluxException with error response
|
||||
if metadata.parsing_state_error:
|
||||
error = csv[1]
|
||||
reference_value = csv[2]
|
||||
raise FluxQueryException(error, reference_value)
|
||||
|
||||
token = csv[0]
|
||||
# start new table
|
||||
if (token in ANNOTATIONS and not metadata.start_new_table) or \
|
||||
(self._response_metadata_mode is FluxResponseMetadataMode.only_names and not metadata.table):
|
||||
|
||||
# Return already parsed DataFrame
|
||||
if (self._serialization_mode is FluxSerializationMode.dataFrame) & hasattr(self, '_data_frame'):
|
||||
df = self._prepare_data_frame()
|
||||
if not self._is_profiler_table(metadata.table):
|
||||
yield df
|
||||
|
||||
metadata.start_new_table = True
|
||||
metadata.table = FluxTable()
|
||||
self._insert_table(metadata.table, metadata.table_index)
|
||||
metadata.table_index = metadata.table_index + 1
|
||||
metadata.table_id = -1
|
||||
elif metadata.table is None:
|
||||
raise FluxCsvParserException("Unable to parse CSV response. FluxTable definition was not found.")
|
||||
|
||||
# # datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,double,string,string,string
|
||||
if ANNOTATION_DATATYPE == token:
|
||||
self.add_data_types(metadata.table, csv)
|
||||
|
||||
elif ANNOTATION_GROUP == token:
|
||||
metadata.groups = csv
|
||||
|
||||
elif ANNOTATION_DEFAULT == token:
|
||||
self.add_default_empty_values(metadata.table, csv)
|
||||
|
||||
else:
|
||||
# parse column names
|
||||
if metadata.start_new_table:
|
||||
# Invokable scripts doesn't supports dialect => all columns are string
|
||||
if not metadata.table.columns and \
|
||||
self._response_metadata_mode is FluxResponseMetadataMode.only_names:
|
||||
self.add_data_types(metadata.table, list(map(lambda column: 'string', csv)))
|
||||
metadata.groups = list(map(lambda column: 'false', csv))
|
||||
self.add_groups(metadata.table, metadata.groups)
|
||||
self.add_column_names_and_tags(metadata.table, csv)
|
||||
metadata.start_new_table = False
|
||||
# Create DataFrame with default values
|
||||
if self._serialization_mode is FluxSerializationMode.dataFrame:
|
||||
from ..extras import pd
|
||||
labels = list(map(lambda it: it.label, metadata.table.columns))
|
||||
self._data_frame = pd.DataFrame(data=[], columns=labels, index=None)
|
||||
pass
|
||||
else:
|
||||
|
||||
# to int conversions todo
|
||||
current_id = int(csv[2])
|
||||
if metadata.table_id == -1:
|
||||
metadata.table_id = current_id
|
||||
|
||||
if metadata.table_id != current_id:
|
||||
# create new table with previous column headers settings
|
||||
flux_columns = metadata.table.columns
|
||||
metadata.table = FluxTable()
|
||||
metadata.table.columns.extend(flux_columns)
|
||||
self._insert_table(metadata.table, metadata.table_index)
|
||||
metadata.table_index = metadata.table_index + 1
|
||||
metadata.table_id = current_id
|
||||
|
||||
flux_record = self.parse_record(metadata.table_index - 1, metadata.table, csv)
|
||||
|
||||
if self._is_profiler_record(flux_record):
|
||||
self._print_profiler_info(flux_record)
|
||||
else:
|
||||
if self._serialization_mode is FluxSerializationMode.tables:
|
||||
self.tables[metadata.table_index - 1].records.append(flux_record)
|
||||
|
||||
if self._serialization_mode is FluxSerializationMode.stream:
|
||||
yield flux_record
|
||||
|
||||
if self._serialization_mode is FluxSerializationMode.dataFrame:
|
||||
self._data_frame_values.append(flux_record.values)
|
||||
pass
|
||||
|
||||
def _prepare_data_frame(self):
|
||||
from ..extras import pd
|
||||
|
||||
# We have to create temporary DataFrame because we want to preserve default column values
|
||||
_temp_df = pd.DataFrame(self._data_frame_values)
|
||||
self._data_frame_values = []
|
||||
|
||||
# Custom DataFrame index
|
||||
if self._data_frame_index:
|
||||
self._data_frame = self._data_frame.set_index(self._data_frame_index)
|
||||
_temp_df = _temp_df.set_index(self._data_frame_index)
|
||||
|
||||
# Append data
|
||||
df = pd.concat([self._data_frame.astype(_temp_df.dtypes), _temp_df])
|
||||
|
||||
if self._use_extension_dtypes:
|
||||
return df.convert_dtypes()
|
||||
return df
|
||||
|
||||
def parse_record(self, table_index, table, csv):
|
||||
"""Parse one record."""
|
||||
record = FluxRecord(table_index)
|
||||
|
||||
for fluxColumn in table.columns:
|
||||
column_name = fluxColumn.label
|
||||
str_val = csv[fluxColumn.index + 1]
|
||||
record.values[column_name] = self._to_value(str_val, fluxColumn)
|
||||
record.row.append(record.values[column_name])
|
||||
|
||||
return record
|
||||
|
||||
def _to_value(self, str_val, column):
|
||||
|
||||
if str_val == '' or str_val is None:
|
||||
default_value = column.default_value
|
||||
if default_value == '' or default_value is None:
|
||||
if self._serialization_mode is FluxSerializationMode.dataFrame:
|
||||
if self._use_extension_dtypes:
|
||||
from ..extras import pd
|
||||
return pd.NA
|
||||
return None
|
||||
return None
|
||||
return self._to_value(default_value, column)
|
||||
|
||||
if "string" == column.data_type:
|
||||
return str_val
|
||||
|
||||
if "boolean" == column.data_type:
|
||||
return "true" == str_val
|
||||
|
||||
if "unsignedLong" == column.data_type or "long" == column.data_type:
|
||||
return int(str_val)
|
||||
|
||||
if "double" == column.data_type:
|
||||
return float(str_val)
|
||||
|
||||
if "base64Binary" == column.data_type:
|
||||
return base64.b64decode(str_val)
|
||||
|
||||
if "dateTime:RFC3339" == column.data_type or "dateTime:RFC3339Nano" == column.data_type:
|
||||
return get_date_helper().parse_date(str_val)
|
||||
|
||||
if "duration" == column.data_type:
|
||||
# todo better type ?
|
||||
return int(str_val)
|
||||
|
||||
@staticmethod
|
||||
def add_data_types(table, data_types):
|
||||
"""Add data types to columns."""
|
||||
for index in range(1, len(data_types)):
|
||||
column_def = FluxColumn(index=index - 1, data_type=data_types[index])
|
||||
table.columns.append(column_def)
|
||||
|
||||
@staticmethod
|
||||
def add_groups(table, csv):
|
||||
"""Add group keys to columns."""
|
||||
i = 1
|
||||
for column in table.columns:
|
||||
column.group = csv[i] == "true"
|
||||
i += 1
|
||||
|
||||
@staticmethod
|
||||
def add_default_empty_values(table, default_values):
|
||||
"""Add default values to columns."""
|
||||
i = 1
|
||||
for column in table.columns:
|
||||
column.default_value = default_values[i]
|
||||
i += 1
|
||||
|
||||
@staticmethod
|
||||
def add_column_names_and_tags(table, csv):
|
||||
"""Add labels to columns."""
|
||||
if len(csv) != len(set(csv)):
|
||||
message = f"""The response contains columns with duplicated names: '{csv}'.
|
||||
|
||||
You should use the 'record.row' to access your data instead of 'record.values' dictionary.
|
||||
"""
|
||||
warnings.warn(message, UserWarning)
|
||||
print(message)
|
||||
i = 1
|
||||
for column in table.columns:
|
||||
column.label = csv[i]
|
||||
i += 1
|
||||
|
||||
def _insert_table(self, table, table_index):
|
||||
if self._serialization_mode is FluxSerializationMode.tables:
|
||||
self.tables.insert(table_index, table)
|
||||
|
||||
def _is_profiler_record(self, flux_record: FluxRecord) -> bool:
|
||||
if not self._profilers:
|
||||
return False
|
||||
|
||||
for profiler in self._profilers:
|
||||
if "_measurement" in flux_record.values and flux_record["_measurement"] == "profiler/" + profiler:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _is_profiler_table(self, table: FluxTable) -> bool:
|
||||
|
||||
if not self._profilers:
|
||||
return False
|
||||
|
||||
return any(filter(lambda column: (column.default_value == "_profiler" and column.label == "result"),
|
||||
table.columns))
|
||||
|
||||
def table_list(self) -> TableList:
|
||||
"""Get the list of flux tables."""
|
||||
if not self._profilers:
|
||||
return self.tables
|
||||
else:
|
||||
return TableList(filter(lambda table: not self._is_profiler_table(table), self.tables))
|
||||
|
||||
def _print_profiler_info(self, flux_record: FluxRecord):
|
||||
if flux_record.get_measurement().startswith("profiler/"):
|
||||
if self._profiler_callback:
|
||||
self._profiler_callback(flux_record)
|
||||
else:
|
||||
msg = "Profiler: " + flux_record.get_measurement()
|
||||
print("\n" + len(msg) * "=")
|
||||
print(msg)
|
||||
print(len(msg) * "=")
|
||||
for name in flux_record.values:
|
||||
val = flux_record[name]
|
||||
if isinstance(val, str) and len(val) > 50:
|
||||
print(f"{name:<20}: \n\n{val}")
|
||||
elif val is not None:
|
||||
print(f"{name:<20}: {val:<20}")
|
||||
|
||||
|
||||
class _StreamReaderToWithAsyncRead:
|
||||
def __init__(self, response):
|
||||
self.response = response
|
||||
self.decoder = codecs.getincrementaldecoder(_UTF_8_encoding)()
|
||||
|
||||
async def read(self, size: int) -> str:
|
||||
raw_bytes = (await self.response.read(size))
|
||||
if not raw_bytes:
|
||||
return self.decoder.decode(b'', final=True)
|
||||
return self.decoder.decode(raw_bytes, final=False)
|
||||
@@ -1,290 +0,0 @@
|
||||
"""
|
||||
Flux employs a basic data model built from basic data types.
|
||||
|
||||
The data model consists of tables, records, columns.
|
||||
"""
|
||||
import codecs
|
||||
import csv
|
||||
from http.client import HTTPResponse
|
||||
from json import JSONEncoder
|
||||
from typing import List, Iterator
|
||||
from influxdb_client.rest import _UTF_8_encoding
|
||||
|
||||
|
||||
class FluxStructure:
|
||||
"""The data model consists of tables, records, columns."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FluxStructureEncoder(JSONEncoder):
|
||||
"""The FluxStructure encoder to encode query results to JSON."""
|
||||
|
||||
def default(self, obj):
|
||||
"""Return serializable objects for JSONEncoder."""
|
||||
import datetime
|
||||
if isinstance(obj, FluxStructure):
|
||||
return obj.__dict__
|
||||
elif isinstance(obj, (datetime.datetime, datetime.date)):
|
||||
return obj.isoformat()
|
||||
return super().default(obj)
|
||||
|
||||
|
||||
class FluxTable(FluxStructure):
|
||||
"""
|
||||
A table is set of records with a common set of columns and a group key.
|
||||
|
||||
The table can be serialized into JSON by::
|
||||
|
||||
import json
|
||||
from influxdb_client.client.flux_table import FluxStructureEncoder
|
||||
|
||||
output = json.dumps(tables, cls=FluxStructureEncoder, indent=2)
|
||||
print(output)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize defaults."""
|
||||
self.columns: List[FluxColumn] = []
|
||||
self.records: List[FluxRecord] = []
|
||||
|
||||
def get_group_key(self):
|
||||
"""
|
||||
Group key is a list of columns.
|
||||
|
||||
A table’s group key denotes which subset of the entire dataset is assigned to the table.
|
||||
"""
|
||||
return list(filter(lambda column: (column.group is True), self.columns))
|
||||
|
||||
def __str__(self):
|
||||
"""Return formatted output."""
|
||||
cls_name = type(self).__name__
|
||||
return cls_name + "() columns: " + str(len(self.columns)) + ", records: " + str(len(self.records))
|
||||
|
||||
def __repr__(self):
|
||||
"""Format for inspection."""
|
||||
return f"<{type(self).__name__}: {len(self.columns)} columns, {len(self.records)} records>"
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate over records."""
|
||||
return iter(self.records)
|
||||
|
||||
|
||||
class FluxColumn(FluxStructure):
|
||||
"""A column has a label and a data type."""
|
||||
|
||||
def __init__(self, index=None, label=None, data_type=None, group=None, default_value=None) -> None:
|
||||
"""Initialize defaults."""
|
||||
self.default_value = default_value
|
||||
self.group = group
|
||||
self.data_type = data_type
|
||||
self.label = label
|
||||
self.index = index
|
||||
|
||||
def __repr__(self):
|
||||
"""Format for inspection."""
|
||||
fields = [repr(self.index)] + [
|
||||
f'{name}={getattr(self, name)!r}' for name in (
|
||||
'label', 'data_type', 'group', 'default_value'
|
||||
) if getattr(self, name) is not None
|
||||
]
|
||||
return f"{type(self).__name__}({', '.join(fields)})"
|
||||
|
||||
|
||||
class FluxRecord(FluxStructure):
|
||||
"""A record is a tuple of named values and is represented using an object type."""
|
||||
|
||||
def __init__(self, table, values=None) -> None:
|
||||
"""Initialize defaults."""
|
||||
if values is None:
|
||||
values = {}
|
||||
self.table = table
|
||||
self.values = values
|
||||
self.row = []
|
||||
|
||||
def get_start(self):
|
||||
"""Get '_start' value."""
|
||||
return self["_start"]
|
||||
|
||||
def get_stop(self):
|
||||
"""Get '_stop' value."""
|
||||
return self["_stop"]
|
||||
|
||||
def get_time(self):
|
||||
"""Get timestamp."""
|
||||
return self["_time"]
|
||||
|
||||
def get_value(self):
|
||||
"""Get field value."""
|
||||
return self["_value"]
|
||||
|
||||
def get_field(self):
|
||||
"""Get field name."""
|
||||
return self["_field"]
|
||||
|
||||
def get_measurement(self):
|
||||
"""Get measurement name."""
|
||||
return self["_measurement"]
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""Get value by key."""
|
||||
return self.values.__getitem__(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Set value with key and value."""
|
||||
return self.values.__setitem__(key, value)
|
||||
|
||||
def __str__(self):
|
||||
"""Return formatted output."""
|
||||
cls_name = type(self).__name__
|
||||
return cls_name + "() table: " + str(self.table) + ", " + str(self.values)
|
||||
|
||||
def __repr__(self):
|
||||
"""Format for inspection."""
|
||||
return f"<{type(self).__name__}: field={self.values.get('_field')}, value={self.values.get('_value')}>"
|
||||
|
||||
|
||||
class TableList(List[FluxTable]):
|
||||
""":class:`~influxdb_client.client.flux_table.FluxTable` list with additionally functional to better handle of query result.""" # noqa: E501
|
||||
|
||||
def to_values(self, columns: List['str'] = None) -> List[List[object]]:
|
||||
"""
|
||||
Serialize query results to a flattened list of values.
|
||||
|
||||
:param columns: if not ``None`` then only specified columns are presented in results
|
||||
:return: :class:`~list` of values
|
||||
|
||||
Output example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['New York', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 24.3],
|
||||
['Prague', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 25.3],
|
||||
...
|
||||
]
|
||||
|
||||
Configure required columns:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to values
|
||||
output = tables.to_values(columns=['location', '_time', '_value'])
|
||||
print(output)
|
||||
"""
|
||||
|
||||
def filter_values(record):
|
||||
if columns is not None:
|
||||
return [record.values.get(k) for k in columns]
|
||||
return record.values.values()
|
||||
|
||||
return self._to_values(filter_values)
|
||||
|
||||
def to_json(self, columns: List['str'] = None, **kwargs) -> str:
|
||||
"""
|
||||
Serialize query results to a JSON formatted :class:`~str`.
|
||||
|
||||
:param columns: if not ``None`` then only specified columns are presented in results
|
||||
:return: :class:`~str`
|
||||
|
||||
The query results is flattened to array:
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:00.897825+00:00",
|
||||
"region": "north",
|
||||
"_field": "usage",
|
||||
"_value": 15
|
||||
},
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:01.897825+00:00",
|
||||
"region": "west",
|
||||
"_field": "usage",
|
||||
"_value": 10
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
The JSON format could be configured via ``**kwargs`` arguments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to JSON
|
||||
output = tables.to_json(indent=5)
|
||||
print(output)
|
||||
|
||||
For all available options see - `json.dump <https://docs.python.org/3/library/json.html#json.dump>`_.
|
||||
"""
|
||||
if 'indent' not in kwargs:
|
||||
kwargs['indent'] = 2
|
||||
|
||||
def filter_values(record):
|
||||
if columns is not None:
|
||||
return {k: v for (k, v) in record.values.items() if k in columns}
|
||||
return record.values
|
||||
|
||||
import json
|
||||
return json.dumps(self._to_values(filter_values), cls=FluxStructureEncoder, **kwargs)
|
||||
|
||||
def _to_values(self, mapping):
|
||||
return [mapping(record) for table in self for record in table.records]
|
||||
|
||||
|
||||
class CSVIterator(Iterator[List[str]]):
|
||||
""":class:`Iterator[List[str]]` with additionally functional to better handle of query result."""
|
||||
|
||||
def __init__(self, response: HTTPResponse) -> None:
|
||||
"""Initialize ``csv.reader``."""
|
||||
self.delegate = csv.reader(codecs.iterdecode(response, _UTF_8_encoding))
|
||||
|
||||
def __iter__(self):
|
||||
"""Return an iterator object."""
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
"""Retrieve the next item from the iterator."""
|
||||
row = self.delegate.__next__()
|
||||
while not row:
|
||||
row = self.delegate.__next__()
|
||||
return row
|
||||
|
||||
def to_values(self) -> List[List[str]]:
|
||||
"""
|
||||
Serialize query results to a flattened list of values.
|
||||
|
||||
:return: :class:`~list` of values
|
||||
|
||||
Output example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['New York', '2022-06-14T08:00:51.749072045Z', '24.3'],
|
||||
['Prague', '2022-06-14T08:00:51.749072045Z', '25.3'],
|
||||
...
|
||||
]
|
||||
"""
|
||||
return list(self.__iter__())
|
||||
@@ -1,438 +0,0 @@
|
||||
"""InfluxDBClient is client for API defined in https://github.com/influxdata/influxdb/blob/master/http/swagger.yml."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from influxdb_client import HealthCheck, HealthService, Ready, ReadyService, PingService, \
|
||||
InvokableScriptsApi
|
||||
from influxdb_client.client._base import _BaseClient
|
||||
from influxdb_client.client.authorizations_api import AuthorizationsApi
|
||||
from influxdb_client.client.bucket_api import BucketsApi
|
||||
from influxdb_client.client.delete_api import DeleteApi
|
||||
from influxdb_client.client.labels_api import LabelsApi
|
||||
from influxdb_client.client.organizations_api import OrganizationsApi
|
||||
from influxdb_client.client.query_api import QueryApi, QueryOptions
|
||||
from influxdb_client.client.tasks_api import TasksApi
|
||||
from influxdb_client.client.users_api import UsersApi
|
||||
from influxdb_client.client.write_api import WriteApi, WriteOptions, PointSettings
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.influxdb_client')
|
||||
|
||||
|
||||
class InfluxDBClient(_BaseClient):
|
||||
"""InfluxDBClient is client for InfluxDB v2."""
|
||||
|
||||
def __init__(self, url, token: str = None, debug=None, timeout=10_000, enable_gzip=False, org: str = None,
|
||||
default_tags: dict = None, **kwargs) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param url: InfluxDB server API url (ex. http://localhost:8086).
|
||||
:param token: ``token`` to authenticate to the InfluxDB API
|
||||
:param debug: enable verbose logging of http requests
|
||||
:param timeout: HTTP client timeout setting for a request specified in milliseconds.
|
||||
If one number provided, it will be total request timeout.
|
||||
It can also be a pair (tuple) of (connection, read) timeouts.
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:param org: organization name (used as a default in Query, Write and Delete API)
|
||||
:key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server.
|
||||
:key str ssl_ca_cert: Set this to customize the certificate file to verify the peer.
|
||||
:key str cert_file: Path to the certificate that will be used for mTLS authentication.
|
||||
:key str cert_key_file: Path to the file contains private key for mTLS certificate.
|
||||
:key str cert_key_password: String or function which returns password for decrypting the mTLS private key.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
:key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key int connection_pool_maxsize: Number of connections to save that can be reused by urllib3.
|
||||
Defaults to "multiprocessing.cpu_count() * 5".
|
||||
:key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
|
||||
except batching writes. As a default there is no one retry strategy.
|
||||
:key bool auth_basic: Set this to true to enable basic authentication when talking to a InfluxDB 1.8.x that
|
||||
does not use auth-enabled but is protected by a reverse proxy with basic authentication.
|
||||
(defaults to false, don't set to true when talking to InfluxDB 2)
|
||||
:key str username: ``username`` to authenticate via username and password credentials to the InfluxDB 2.x
|
||||
:key str password: ``password`` to authenticate via username and password credentials to the InfluxDB 2.x
|
||||
:key list[str] profilers: list of enabled Flux profilers
|
||||
"""
|
||||
super().__init__(url=url, token=token, debug=debug, timeout=timeout, enable_gzip=enable_gzip, org=org,
|
||||
default_tags=default_tags, http_client_logger="urllib3", **kwargs)
|
||||
|
||||
from .._sync.api_client import ApiClient
|
||||
self.api_client = ApiClient(configuration=self.conf, header_name=self.auth_header_name,
|
||||
header_value=self.auth_header_value, retries=self.retries)
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Enter the runtime context related to this object.
|
||||
|
||||
It will bind this method’s return value to the target(s)
|
||||
specified in the `as` clause of the statement.
|
||||
|
||||
return: self instance
|
||||
"""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
"""Exit the runtime context related to this object and close the client."""
|
||||
self.close()
|
||||
|
||||
@classmethod
|
||||
def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gzip=False, **kwargs):
|
||||
"""
|
||||
Configure client via configuration file. The configuration has to be under 'influx' section.
|
||||
|
||||
:param config_file: Path to configuration file
|
||||
:param debug: Enable verbose logging of http requests
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:key config_name: Name of the configuration section of the configuration file
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
|
||||
except batching writes. As a default there is no one retry strategy.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
|
||||
The supported formats:
|
||||
- https://docs.python.org/3/library/configparser.html
|
||||
- https://toml.io/en/
|
||||
- https://www.json.org/json-en.html
|
||||
|
||||
Configuration options:
|
||||
- url
|
||||
- org
|
||||
- token
|
||||
- timeout,
|
||||
- verify_ssl
|
||||
- ssl_ca_cert
|
||||
- cert_file
|
||||
- cert_key_file
|
||||
- cert_key_password
|
||||
- connection_pool_maxsize
|
||||
- auth_basic
|
||||
- profilers
|
||||
- proxy
|
||||
|
||||
|
||||
config.ini example::
|
||||
|
||||
[influx2]
|
||||
url=http://localhost:8086
|
||||
org=my-org
|
||||
token=my-token
|
||||
timeout=6000
|
||||
connection_pool_maxsize=25
|
||||
auth_basic=false
|
||||
profilers=query,operator
|
||||
proxy=http:proxy.domain.org:8080
|
||||
|
||||
[tags]
|
||||
id = 132-987-655
|
||||
customer = California Miner
|
||||
data_center = ${env.data_center}
|
||||
|
||||
config.toml example::
|
||||
|
||||
[influx2]
|
||||
url = "http://localhost:8086"
|
||||
token = "my-token"
|
||||
org = "my-org"
|
||||
timeout = 6000
|
||||
connection_pool_maxsize = 25
|
||||
auth_basic = false
|
||||
profilers="query, operator"
|
||||
proxy = "http://proxy.domain.org:8080"
|
||||
|
||||
[tags]
|
||||
id = "132-987-655"
|
||||
customer = "California Miner"
|
||||
data_center = "${env.data_center}"
|
||||
|
||||
config.json example::
|
||||
|
||||
{
|
||||
"url": "http://localhost:8086",
|
||||
"token": "my-token",
|
||||
"org": "my-org",
|
||||
"active": true,
|
||||
"timeout": 6000,
|
||||
"connection_pool_maxsize": 55,
|
||||
"auth_basic": false,
|
||||
"profilers": "query, operator",
|
||||
"tags": {
|
||||
"id": "132-987-655",
|
||||
"customer": "California Miner",
|
||||
"data_center": "${env.data_center}"
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
return InfluxDBClient._from_config_file(config_file=config_file, debug=debug, enable_gzip=enable_gzip, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs):
|
||||
"""
|
||||
Configure client via environment properties.
|
||||
|
||||
:param debug: Enable verbose logging of http requests
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
|
||||
except batching writes. As a default there is no one retry strategy.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
|
||||
Supported environment properties:
|
||||
- INFLUXDB_V2_URL
|
||||
- INFLUXDB_V2_ORG
|
||||
- INFLUXDB_V2_TOKEN
|
||||
- INFLUXDB_V2_TIMEOUT
|
||||
- INFLUXDB_V2_VERIFY_SSL
|
||||
- INFLUXDB_V2_SSL_CA_CERT
|
||||
- INFLUXDB_V2_CERT_FILE
|
||||
- INFLUXDB_V2_CERT_KEY_FILE
|
||||
- INFLUXDB_V2_CERT_KEY_PASSWORD
|
||||
- INFLUXDB_V2_CONNECTION_POOL_MAXSIZE
|
||||
- INFLUXDB_V2_AUTH_BASIC
|
||||
- INFLUXDB_V2_PROFILERS
|
||||
- INFLUXDB_V2_TAG
|
||||
"""
|
||||
return InfluxDBClient._from_env_properties(debug=debug, enable_gzip=enable_gzip, **kwargs)
|
||||
|
||||
def write_api(self, write_options=WriteOptions(), point_settings=PointSettings(), **kwargs) -> WriteApi:
|
||||
"""
|
||||
Create Write API instance.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
|
||||
|
||||
# Initialize SYNCHRONOUS instance of WriteApi
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
|
||||
If you would like to use a **background batching**, you have to configure client like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
# Initialize background batching instance of WriteApi
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
with client.write_api() as write_api:
|
||||
pass
|
||||
|
||||
There is also possibility to use callbacks to notify about state of background batches:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
|
||||
|
||||
class BatchingCallback(object):
|
||||
|
||||
def success(self, conf: (str, str, str), data: str):
|
||||
print(f"Written batch: {conf}, data: {data}")
|
||||
|
||||
def error(self, conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
print(f"Cannot write batch: {conf}, data: {data} due: {exception}")
|
||||
|
||||
def retry(self, conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
print(f"Retryable error occurs for batch: {conf}, data: {data} retry: {exception}")
|
||||
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
callback = BatchingCallback()
|
||||
with client.write_api(success_callback=callback.success,
|
||||
error_callback=callback.error,
|
||||
retry_callback=callback.retry) as write_api:
|
||||
pass
|
||||
|
||||
:param write_options: Write API configuration
|
||||
:param point_settings: settings to store default tags
|
||||
:key success_callback: The callable ``callback`` to run after having successfully written a batch.
|
||||
|
||||
The callable must accept two arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
|
||||
**[batching mode]**
|
||||
|
||||
:key error_callback: The callable ``callback`` to run after having unsuccessfully written a batch.
|
||||
|
||||
The callable must accept three arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
- `Exception`: an occurred error
|
||||
|
||||
**[batching mode]**
|
||||
:key retry_callback: The callable ``callback`` to run after retryable error occurred.
|
||||
|
||||
The callable must accept three arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
- `Exception`: an retryable error
|
||||
|
||||
**[batching mode]**
|
||||
:return: write api instance
|
||||
"""
|
||||
return WriteApi(influxdb_client=self, write_options=write_options, point_settings=point_settings, **kwargs)
|
||||
|
||||
def query_api(self, query_options: QueryOptions = QueryOptions()) -> QueryApi:
|
||||
"""
|
||||
Create an Query API instance.
|
||||
|
||||
:param query_options: optional query api configuration
|
||||
:return: Query api instance
|
||||
"""
|
||||
return QueryApi(self, query_options)
|
||||
|
||||
def invokable_scripts_api(self) -> InvokableScriptsApi:
|
||||
"""
|
||||
Create an InvokableScripts API instance.
|
||||
|
||||
:return: InvokableScripts API instance
|
||||
"""
|
||||
return InvokableScriptsApi(self)
|
||||
|
||||
def close(self):
|
||||
"""Shutdown the client."""
|
||||
self.__del__()
|
||||
|
||||
def __del__(self):
|
||||
"""Shutdown the client."""
|
||||
if self.api_client:
|
||||
self.api_client.__del__()
|
||||
self.api_client = None
|
||||
|
||||
def buckets_api(self) -> BucketsApi:
|
||||
"""
|
||||
Create the Bucket API instance.
|
||||
|
||||
:return: buckets api
|
||||
"""
|
||||
return BucketsApi(self)
|
||||
|
||||
def authorizations_api(self) -> AuthorizationsApi:
|
||||
"""
|
||||
Create the Authorizations API instance.
|
||||
|
||||
:return: authorizations api
|
||||
"""
|
||||
return AuthorizationsApi(self)
|
||||
|
||||
def users_api(self) -> UsersApi:
|
||||
"""
|
||||
Create the Users API instance.
|
||||
|
||||
:return: users api
|
||||
"""
|
||||
return UsersApi(self)
|
||||
|
||||
def organizations_api(self) -> OrganizationsApi:
|
||||
"""
|
||||
Create the Organizations API instance.
|
||||
|
||||
:return: organizations api
|
||||
"""
|
||||
return OrganizationsApi(self)
|
||||
|
||||
def tasks_api(self) -> TasksApi:
|
||||
"""
|
||||
Create the Tasks API instance.
|
||||
|
||||
:return: tasks api
|
||||
"""
|
||||
return TasksApi(self)
|
||||
|
||||
def labels_api(self) -> LabelsApi:
|
||||
"""
|
||||
Create the Labels API instance.
|
||||
|
||||
:return: labels api
|
||||
"""
|
||||
return LabelsApi(self)
|
||||
|
||||
def health(self) -> HealthCheck:
|
||||
"""
|
||||
Get the health of an instance.
|
||||
|
||||
:return: HealthCheck
|
||||
"""
|
||||
warnings.warn("This method is deprecated. Call 'ping()' instead.", DeprecationWarning)
|
||||
health_service = HealthService(self.api_client)
|
||||
|
||||
try:
|
||||
health = health_service.get_health()
|
||||
return health
|
||||
except Exception as e:
|
||||
return HealthCheck(name="influxdb", message=str(e), status="fail")
|
||||
|
||||
def ping(self) -> bool:
|
||||
"""
|
||||
Return the status of InfluxDB instance.
|
||||
|
||||
:return: The status of InfluxDB.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
try:
|
||||
ping_service.get_ping()
|
||||
return True
|
||||
except Exception as ex:
|
||||
logger.debug("Unexpected error during /ping: %s", ex)
|
||||
return False
|
||||
|
||||
def version(self) -> str:
|
||||
"""
|
||||
Return the version of the connected InfluxDB Server.
|
||||
|
||||
:return: The version of InfluxDB.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
response = ping_service.get_ping_with_http_info(_return_http_data_only=False)
|
||||
|
||||
return ping_service.response_header(response)
|
||||
|
||||
def build(self) -> str:
|
||||
"""
|
||||
Return the build type of the connected InfluxDB Server.
|
||||
|
||||
:return: The type of InfluxDB build.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
return ping_service.build_type()
|
||||
|
||||
def ready(self) -> Ready:
|
||||
"""
|
||||
Get The readiness of the InfluxDB 2.0.
|
||||
|
||||
:return: Ready
|
||||
"""
|
||||
ready_service = ReadyService(self.api_client)
|
||||
return ready_service.get_ready()
|
||||
|
||||
def delete_api(self) -> DeleteApi:
|
||||
"""
|
||||
Get the delete metrics API instance.
|
||||
|
||||
:return: delete api
|
||||
"""
|
||||
return DeleteApi(self)
|
||||
@@ -1,301 +0,0 @@
|
||||
"""InfluxDBClientAsync is client for API defined in https://github.com/influxdata/openapi/blob/master/contracts/oss.yml.""" # noqa: E501
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from influxdb_client import PingService
|
||||
from influxdb_client.client._base import _BaseClient
|
||||
from influxdb_client.client.delete_api_async import DeleteApiAsync
|
||||
from influxdb_client.client.query_api import QueryOptions
|
||||
from influxdb_client.client.query_api_async import QueryApiAsync
|
||||
from influxdb_client.client.write_api import PointSettings
|
||||
from influxdb_client.client.write_api_async import WriteApiAsync
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.influxdb_client_async')
|
||||
|
||||
|
||||
class InfluxDBClientAsync(_BaseClient):
|
||||
"""InfluxDBClientAsync is client for InfluxDB v2."""
|
||||
|
||||
def __init__(self, url, token: str = None, org: str = None, debug=None, timeout=10_000, enable_gzip=False,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param url: InfluxDB server API url (ex. http://localhost:8086).
|
||||
:param token: ``token`` to authenticate to the InfluxDB 2.x
|
||||
:param org: organization name (used as a default in Query, Write and Delete API)
|
||||
:param debug: enable verbose logging of http requests
|
||||
:param timeout: The maximal number of milliseconds for the whole HTTP request including
|
||||
connection establishment, request sending and response reading.
|
||||
It can also be a :class:`~aiohttp.ClientTimeout` which is directly pass to ``aiohttp``.
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:key bool verify_ssl: Set this to false to skip verifying SSL certificate when calling API from https server.
|
||||
:key str ssl_ca_cert: Set this to customize the certificate file to verify the peer.
|
||||
:key str cert_file: Path to the certificate that will be used for mTLS authentication.
|
||||
:key str cert_key_file: Path to the file contains private key for mTLS certificate.
|
||||
:key str cert_key_password: String or function which returns password for decrypting the mTLS private key.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
:key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key int connection_pool_maxsize: The total number of simultaneous connections.
|
||||
Defaults to "multiprocessing.cpu_count() * 5".
|
||||
:key bool auth_basic: Set this to true to enable basic authentication when talking to a InfluxDB 1.8.x that
|
||||
does not use auth-enabled but is protected by a reverse proxy with basic authentication.
|
||||
(defaults to false, don't set to true when talking to InfluxDB 2)
|
||||
:key str username: ``username`` to authenticate via username and password credentials to the InfluxDB 2.x
|
||||
:key str password: ``password`` to authenticate via username and password credentials to the InfluxDB 2.x
|
||||
:key bool allow_redirects: If set to ``False``, do not follow HTTP redirects. ``True`` by default.
|
||||
:key int max_redirects: Maximum number of HTTP redirects to follow. ``10`` by default.
|
||||
:key dict client_session_kwargs: Additional configuration arguments for :class:`~aiohttp.ClientSession`
|
||||
:key type client_session_type: Type of aiohttp client to use. Useful for third party wrappers like
|
||||
``aiohttp-retry``. :class:`~aiohttp.ClientSession` by default.
|
||||
:key list[str] profilers: list of enabled Flux profilers
|
||||
"""
|
||||
super().__init__(url=url, token=token, org=org, debug=debug, timeout=timeout, enable_gzip=enable_gzip,
|
||||
http_client_logger="aiohttp.client", **kwargs)
|
||||
|
||||
# compatibility with Python 3.6
|
||||
if sys.version_info[:2] >= (3, 7):
|
||||
from asyncio import get_running_loop
|
||||
else:
|
||||
from asyncio import _get_running_loop as get_running_loop
|
||||
|
||||
# check present asynchronous context
|
||||
try:
|
||||
loop = get_running_loop()
|
||||
# compatibility with Python 3.6
|
||||
if loop is None:
|
||||
raise RuntimeError('no running event loop')
|
||||
except RuntimeError:
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
message = "The async client should be initialised inside async coroutine " \
|
||||
"otherwise there can be unexpected behaviour."
|
||||
raise InfluxDBError(response=None, message=message)
|
||||
|
||||
from .._async.api_client import ApiClientAsync
|
||||
self.api_client = ApiClientAsync(configuration=self.conf, header_name=self.auth_header_name,
|
||||
header_value=self.auth_header_value, **kwargs)
|
||||
|
||||
async def __aenter__(self) -> 'InfluxDBClientAsync':
|
||||
"""
|
||||
Enter the runtime context related to this object.
|
||||
|
||||
return: self instance
|
||||
"""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb) -> None:
|
||||
"""Shutdown the client."""
|
||||
await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""Shutdown the client."""
|
||||
if self.api_client:
|
||||
await self.api_client.close()
|
||||
self.api_client = None
|
||||
|
||||
@classmethod
|
||||
def from_config_file(cls, config_file: str = "config.ini", debug=None, enable_gzip=False, **kwargs):
|
||||
"""
|
||||
Configure client via configuration file. The configuration has to be under 'influx' section.
|
||||
|
||||
:param config_file: Path to configuration file
|
||||
:param debug: Enable verbose logging of http requests
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:key config_name: Name of the configuration section of the configuration file
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
|
||||
except batching writes. As a default there is no one retry strategy.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
|
||||
The supported formats:
|
||||
- https://docs.python.org/3/library/configparser.html
|
||||
- https://toml.io/en/
|
||||
- https://www.json.org/json-en.html
|
||||
|
||||
Configuration options:
|
||||
- url
|
||||
- org
|
||||
- token
|
||||
- timeout,
|
||||
- verify_ssl
|
||||
- ssl_ca_cert
|
||||
- cert_file
|
||||
- cert_key_file
|
||||
- cert_key_password
|
||||
- connection_pool_maxsize
|
||||
- auth_basic
|
||||
- profilers
|
||||
- proxy
|
||||
|
||||
|
||||
config.ini example::
|
||||
|
||||
[influx2]
|
||||
url=http://localhost:8086
|
||||
org=my-org
|
||||
token=my-token
|
||||
timeout=6000
|
||||
connection_pool_maxsize=25
|
||||
auth_basic=false
|
||||
profilers=query,operator
|
||||
proxy=http:proxy.domain.org:8080
|
||||
|
||||
[tags]
|
||||
id = 132-987-655
|
||||
customer = California Miner
|
||||
data_center = ${env.data_center}
|
||||
|
||||
config.toml example::
|
||||
|
||||
[influx2]
|
||||
url = "http://localhost:8086"
|
||||
token = "my-token"
|
||||
org = "my-org"
|
||||
timeout = 6000
|
||||
connection_pool_maxsize = 25
|
||||
auth_basic = false
|
||||
profilers="query, operator"
|
||||
proxy = "http://proxy.domain.org:8080"
|
||||
|
||||
[tags]
|
||||
id = "132-987-655"
|
||||
customer = "California Miner"
|
||||
data_center = "${env.data_center}"
|
||||
|
||||
config.json example::
|
||||
|
||||
{
|
||||
"url": "http://localhost:8086",
|
||||
"token": "my-token",
|
||||
"org": "my-org",
|
||||
"active": true,
|
||||
"timeout": 6000,
|
||||
"connection_pool_maxsize": 55,
|
||||
"auth_basic": false,
|
||||
"profilers": "query, operator",
|
||||
"tags": {
|
||||
"id": "132-987-655",
|
||||
"customer": "California Miner",
|
||||
"data_center": "${env.data_center}"
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
return InfluxDBClientAsync._from_config_file(config_file=config_file, debug=debug,
|
||||
enable_gzip=enable_gzip, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_env_properties(cls, debug=None, enable_gzip=False, **kwargs):
|
||||
"""
|
||||
Configure client via environment properties.
|
||||
|
||||
:param debug: Enable verbose logging of http requests
|
||||
:param enable_gzip: Enable Gzip compression for http requests. Currently, only the "Write" and "Query" endpoints
|
||||
supports the Gzip compression.
|
||||
:key str proxy: Set this to configure the http proxy to be used (ex. http://localhost:3128)
|
||||
:key str proxy_headers: A dictionary containing headers that will be sent to the proxy. Could be used for proxy
|
||||
authentication.
|
||||
:key urllib3.util.retry.Retry retries: Set the default retry strategy that is used for all HTTP requests
|
||||
except batching writes. As a default there is no one retry strategy.
|
||||
:key ssl.SSLContext ssl_context: Specify a custom Python SSL Context for the TLS/ mTLS handshake.
|
||||
Be aware that only delivered certificate/ key files or an SSL Context are
|
||||
possible.
|
||||
|
||||
|
||||
Supported environment properties:
|
||||
- INFLUXDB_V2_URL
|
||||
- INFLUXDB_V2_ORG
|
||||
- INFLUXDB_V2_TOKEN
|
||||
- INFLUXDB_V2_TIMEOUT
|
||||
- INFLUXDB_V2_VERIFY_SSL
|
||||
- INFLUXDB_V2_SSL_CA_CERT
|
||||
- INFLUXDB_V2_CERT_FILE
|
||||
- INFLUXDB_V2_CERT_KEY_FILE
|
||||
- INFLUXDB_V2_CERT_KEY_PASSWORD
|
||||
- INFLUXDB_V2_CONNECTION_POOL_MAXSIZE
|
||||
- INFLUXDB_V2_AUTH_BASIC
|
||||
- INFLUXDB_V2_PROFILERS
|
||||
- INFLUXDB_V2_TAG
|
||||
"""
|
||||
return InfluxDBClientAsync._from_env_properties(debug=debug, enable_gzip=enable_gzip, **kwargs)
|
||||
|
||||
async def ping(self) -> bool:
|
||||
"""
|
||||
Return the status of InfluxDB instance.
|
||||
|
||||
:return: The status of InfluxDB.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
try:
|
||||
await ping_service.get_ping_async()
|
||||
return True
|
||||
except Exception as ex:
|
||||
logger.debug("Unexpected error during /ping: %s", ex)
|
||||
raise ex
|
||||
|
||||
async def version(self) -> str:
|
||||
"""
|
||||
Return the version of the connected InfluxDB Server.
|
||||
|
||||
:return: The version of InfluxDB.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
response = await ping_service.get_ping_async(_return_http_data_only=False)
|
||||
return ping_service.response_header(response)
|
||||
|
||||
async def build(self) -> str:
|
||||
"""
|
||||
Return the build type of the connected InfluxDB Server.
|
||||
|
||||
:return: The type of InfluxDB build.
|
||||
"""
|
||||
ping_service = PingService(self.api_client)
|
||||
|
||||
return await ping_service.build_type_async()
|
||||
|
||||
def query_api(self, query_options: QueryOptions = QueryOptions()) -> QueryApiAsync:
|
||||
"""
|
||||
Create an asynchronous Query API instance.
|
||||
|
||||
:param query_options: optional query api configuration
|
||||
:return: Query api instance
|
||||
"""
|
||||
return QueryApiAsync(self, query_options)
|
||||
|
||||
def write_api(self, point_settings=PointSettings()) -> WriteApiAsync:
|
||||
"""
|
||||
Create an asynchronous Write API instance.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client_async import InfluxDBClientAsync
|
||||
|
||||
|
||||
# Initialize async/await instance of Write API
|
||||
async with InfluxDBClientAsync(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
write_api = client.write_api()
|
||||
|
||||
:param point_settings: settings to store default tags
|
||||
:return: write api instance
|
||||
"""
|
||||
return WriteApiAsync(influxdb_client=self, point_settings=point_settings)
|
||||
|
||||
def delete_api(self) -> DeleteApiAsync:
|
||||
"""
|
||||
Get the asynchronous delete metrics API instance.
|
||||
|
||||
:return: delete api
|
||||
"""
|
||||
return DeleteApiAsync(self)
|
||||
@@ -1,293 +0,0 @@
|
||||
"""
|
||||
Use API invokable scripts to create custom InfluxDB API endpoints that query, process, and shape data.
|
||||
|
||||
API invokable scripts let you assign scripts to API endpoints and then execute them as standard REST operations
|
||||
in InfluxDB Cloud.
|
||||
"""
|
||||
|
||||
from typing import List, Iterator, Generator, Any
|
||||
|
||||
from influxdb_client import Script, InvokableScriptsService, ScriptCreateRequest, ScriptUpdateRequest, \
|
||||
ScriptInvocationParams
|
||||
from influxdb_client.client._base import _BaseQueryApi
|
||||
from influxdb_client.client.flux_csv_parser import FluxResponseMetadataMode
|
||||
from influxdb_client.client.flux_table import FluxRecord, TableList, CSVIterator
|
||||
|
||||
|
||||
class InvokableScriptsApi(_BaseQueryApi):
|
||||
"""Use API invokable scripts to create custom InfluxDB API endpoints that query, process, and shape data."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._invokable_scripts_service = InvokableScriptsService(influxdb_client.api_client)
|
||||
|
||||
def create_script(self, create_request: ScriptCreateRequest) -> Script:
|
||||
"""Create a script.
|
||||
|
||||
:param ScriptCreateRequest create_request: The script to create. (required)
|
||||
:return: The created script.
|
||||
"""
|
||||
return self._invokable_scripts_service.post_scripts(script_create_request=create_request)
|
||||
|
||||
def update_script(self, script_id: str, update_request: ScriptUpdateRequest) -> Script:
|
||||
"""Update a script.
|
||||
|
||||
:param str script_id: The ID of the script to update. (required)
|
||||
:param ScriptUpdateRequest update_request: Script updates to apply (required)
|
||||
:return: The updated.
|
||||
"""
|
||||
return self._invokable_scripts_service.patch_scripts_id(script_id=script_id,
|
||||
script_update_request=update_request)
|
||||
|
||||
def delete_script(self, script_id: str) -> None:
|
||||
"""Delete a script.
|
||||
|
||||
:param str script_id: The ID of the script to delete. (required)
|
||||
:return: None
|
||||
"""
|
||||
self._invokable_scripts_service.delete_scripts_id(script_id=script_id)
|
||||
|
||||
def find_scripts(self, **kwargs):
|
||||
"""List scripts.
|
||||
|
||||
:key int limit: The number of scripts to return.
|
||||
:key int offset: The offset for pagination.
|
||||
:return: List of scripts.
|
||||
:rtype: list[Script]
|
||||
"""
|
||||
return self._invokable_scripts_service.get_scripts(**kwargs).scripts
|
||||
|
||||
def invoke_script(self, script_id: str, params: dict = None) -> TableList:
|
||||
"""
|
||||
Invoke synchronously a script and return result as a TableList.
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param params: bind parameters
|
||||
:return: :class:`~influxdb_client.client.flux_table.FluxTable` list wrapped into
|
||||
:class:`~influxdb_client.client.flux_table.TableList`
|
||||
:rtype: TableList
|
||||
|
||||
Serialization the query results to flattened list of values via :func:`~influxdb_client.client.flux_table.TableList.to_values`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="https://us-west-2-1.aws.cloud2.influxdata.com", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.invokable_scripts_api().invoke_script(script_id="script-id")
|
||||
|
||||
# Serialize to values
|
||||
output = tables.to_values(columns=['location', '_time', '_value'])
|
||||
print(output)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['New York', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 24.3],
|
||||
['Prague', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 25.3],
|
||||
...
|
||||
]
|
||||
|
||||
Serialization the query results to JSON via :func:`~influxdb_client.client.flux_table.TableList.to_json`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="https://us-west-2-1.aws.cloud2.influxdata.com", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.invokable_scripts_api().invoke_script(script_id="script-id")
|
||||
|
||||
# Serialize to JSON
|
||||
output = tables.to_json(indent=5)
|
||||
print(output)
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:00.897825+00:00",
|
||||
"region": "north",
|
||||
"_field": "usage",
|
||||
"_value": 15
|
||||
},
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:01.897825+00:00",
|
||||
"region": "west",
|
||||
"_field": "usage",
|
||||
"_value": 10
|
||||
},
|
||||
...
|
||||
]
|
||||
""" # noqa: E501
|
||||
response = self._invokable_scripts_service \
|
||||
.post_scripts_id_invoke(script_id=script_id,
|
||||
script_invocation_params=ScriptInvocationParams(params=params),
|
||||
async_req=False,
|
||||
_preload_content=False,
|
||||
_return_http_data_only=False)
|
||||
return self._to_tables(response, query_options=None, response_metadata_mode=FluxResponseMetadataMode.only_names)
|
||||
|
||||
def invoke_script_stream(self, script_id: str, params: dict = None) -> Generator['FluxRecord', Any, None]:
|
||||
"""
|
||||
Invoke synchronously a script and return result as a Generator['FluxRecord'].
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param params: bind parameters
|
||||
:return: Stream of FluxRecord.
|
||||
:rtype: Generator['FluxRecord']
|
||||
"""
|
||||
response = self._invokable_scripts_service \
|
||||
.post_scripts_id_invoke(script_id=script_id,
|
||||
script_invocation_params=ScriptInvocationParams(params=params),
|
||||
async_req=False,
|
||||
_preload_content=False,
|
||||
_return_http_data_only=False)
|
||||
|
||||
return self._to_flux_record_stream(response, query_options=None,
|
||||
response_metadata_mode=FluxResponseMetadataMode.only_names)
|
||||
|
||||
def invoke_script_data_frame(self, script_id: str, params: dict = None, data_frame_index: List[str] = None):
|
||||
"""
|
||||
Invoke synchronously a script and return Pandas DataFrame.
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
.. note:: If the ``script`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param List[str] data_frame_index: The list of columns that are used as DataFrame index.
|
||||
:param params: bind parameters
|
||||
:return: :class:`~DataFrame` or :class:`~List[DataFrame]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
_generator = self.invoke_script_data_frame_stream(script_id=script_id,
|
||||
params=params,
|
||||
data_frame_index=data_frame_index)
|
||||
return self._to_data_frames(_generator)
|
||||
|
||||
def invoke_script_data_frame_stream(self, script_id: str, params: dict = None, data_frame_index: List[str] = None):
|
||||
"""
|
||||
Invoke synchronously a script and return stream of Pandas DataFrame as a Generator['pd.DataFrame'].
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
.. note:: If the ``script`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param List[str] data_frame_index: The list of columns that are used as DataFrame index.
|
||||
:param params: bind parameters
|
||||
:return: :class:`~Generator[DataFrame]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
response = self._invokable_scripts_service \
|
||||
.post_scripts_id_invoke(script_id=script_id,
|
||||
script_invocation_params=ScriptInvocationParams(params=params),
|
||||
async_req=False,
|
||||
_preload_content=False,
|
||||
_return_http_data_only=False)
|
||||
|
||||
return self._to_data_frame_stream(data_frame_index, response, query_options=None,
|
||||
response_metadata_mode=FluxResponseMetadataMode.only_names)
|
||||
|
||||
def invoke_script_csv(self, script_id: str, params: dict = None) -> CSVIterator:
|
||||
"""
|
||||
Invoke synchronously a script and return result as a CSV iterator. Each iteration returns a row of the CSV file.
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param params: bind parameters
|
||||
:return: :class:`~Iterator[List[str]]` wrapped into :class:`~influxdb_client.client.flux_table.CSVIterator`
|
||||
:rtype: CSVIterator
|
||||
|
||||
Serialization the query results to flattened list of values via :func:`~influxdb_client.client.flux_table.CSVIterator.to_values`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using CSV iterator
|
||||
csv_iterator = client.invokable_scripts_api().invoke_script_csv(script_id="script-id")
|
||||
|
||||
# Serialize to values
|
||||
output = csv_iterator.to_values()
|
||||
print(output)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['', 'result', 'table', '_start', '_stop', '_time', '_value', '_field', '_measurement', 'location']
|
||||
['', '', '0', '2022-06-16', '2022-06-16', '2022-06-16', '24.3', 'temperature', 'my_measurement', 'New York']
|
||||
['', '', '1', '2022-06-16', '2022-06-16', '2022-06-16', '25.3', 'temperature', 'my_measurement', 'Prague']
|
||||
...
|
||||
]
|
||||
|
||||
""" # noqa: E501
|
||||
response = self._invokable_scripts_service \
|
||||
.post_scripts_id_invoke(script_id=script_id,
|
||||
script_invocation_params=ScriptInvocationParams(params=params),
|
||||
async_req=False,
|
||||
_preload_content=False)
|
||||
|
||||
return self._to_csv(response)
|
||||
|
||||
def invoke_script_raw(self, script_id: str, params: dict = None) -> Iterator[List[str]]:
|
||||
"""
|
||||
Invoke synchronously a script and return result as raw unprocessed result as a str.
|
||||
|
||||
The bind parameters referenced in the script are substitutes with `params` key-values sent in the request body.
|
||||
|
||||
:param str script_id: The ID of the script to invoke. (required)
|
||||
:param params: bind parameters
|
||||
:return: Result as a str.
|
||||
"""
|
||||
response = self._invokable_scripts_service \
|
||||
.post_scripts_id_invoke(script_id=script_id,
|
||||
script_invocation_params=ScriptInvocationParams(params=params),
|
||||
async_req=False,
|
||||
_preload_content=True)
|
||||
|
||||
return response
|
||||
@@ -1,96 +0,0 @@
|
||||
"""Labels are a way to add visual metadata to dashboards, tasks, and other items in the InfluxDB UI."""
|
||||
|
||||
from typing import List, Dict, Union
|
||||
|
||||
from influxdb_client import LabelsService, LabelCreateRequest, Label, LabelUpdate
|
||||
|
||||
|
||||
class LabelsApi(object):
|
||||
"""Implementation for '/api/v2/labels' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._service = LabelsService(influxdb_client.api_client)
|
||||
|
||||
def create_label(self, name: str, org_id: str, properties: Dict[str, str] = None) -> Label:
|
||||
"""
|
||||
Create a new label.
|
||||
|
||||
:param name: label name
|
||||
:param org_id: organization id
|
||||
:param properties: optional label properties
|
||||
:return: created label
|
||||
"""
|
||||
label_request = LabelCreateRequest(org_id=org_id, name=name, properties=properties)
|
||||
return self._service.post_labels(label_create_request=label_request).label
|
||||
|
||||
def update_label(self, label: Label):
|
||||
"""
|
||||
Update an existing label name and properties.
|
||||
|
||||
:param label: label
|
||||
:return: the updated label
|
||||
"""
|
||||
label_update = LabelUpdate()
|
||||
label_update.properties = label.properties
|
||||
label_update.name = label.name
|
||||
return self._service.patch_labels_id(label_id=label.id, label_update=label_update).label
|
||||
|
||||
def delete_label(self, label: Union[str, Label]):
|
||||
"""
|
||||
Delete the label.
|
||||
|
||||
:param label: label id or Label
|
||||
"""
|
||||
label_id = None
|
||||
|
||||
if isinstance(label, str):
|
||||
label_id = label
|
||||
|
||||
if isinstance(label, Label):
|
||||
label_id = label.id
|
||||
|
||||
return self._service.delete_labels_id(label_id=label_id)
|
||||
|
||||
def clone_label(self, cloned_name: str, label: Label) -> Label:
|
||||
"""
|
||||
Create the new instance of the label as a copy existing label.
|
||||
|
||||
:param cloned_name: new label name
|
||||
:param label: existing label
|
||||
:return: clonned Label
|
||||
"""
|
||||
cloned_properties = None
|
||||
if label.properties is not None:
|
||||
cloned_properties = label.properties.copy()
|
||||
|
||||
return self.create_label(name=cloned_name, properties=cloned_properties, org_id=label.org_id)
|
||||
|
||||
def find_labels(self, **kwargs) -> List['Label']:
|
||||
"""
|
||||
Get all available labels.
|
||||
|
||||
:key str org_id: The organization ID.
|
||||
|
||||
:return: labels
|
||||
"""
|
||||
return self._service.get_labels(**kwargs).labels
|
||||
|
||||
def find_label_by_id(self, label_id: str):
|
||||
"""
|
||||
Retrieve the label by id.
|
||||
|
||||
:param label_id:
|
||||
:return: Label
|
||||
"""
|
||||
return self._service.get_labels_id(label_id=label_id).label
|
||||
|
||||
def find_label_by_org(self, org_id) -> List['Label']:
|
||||
"""
|
||||
Get the list of all labels for given organization.
|
||||
|
||||
:param org_id: organization id
|
||||
:return: list of labels
|
||||
"""
|
||||
return self._service.get_labels(org_id=org_id).labels
|
||||
@@ -1,64 +0,0 @@
|
||||
"""Use the influxdb_client with python native logging."""
|
||||
import logging
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
|
||||
class InfluxLoggingHandler(logging.Handler):
|
||||
"""
|
||||
InfluxLoggingHandler instances dispatch logging events to influx.
|
||||
|
||||
There is no need to set a Formatter.
|
||||
The raw input will be passed on to the influx write api.
|
||||
"""
|
||||
|
||||
DEFAULT_LOG_RECORD_KEYS = list(logging.makeLogRecord({}).__dict__.keys()) + ['message']
|
||||
|
||||
def __init__(self, *, url, token, org, bucket, client_args=None, write_api_args=None):
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
The arguments `client_args` and `write_api_args` can be dicts of kwargs.
|
||||
They are passed on to the InfluxDBClient and write_api calls respectively.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.bucket = bucket
|
||||
|
||||
client_args = {} if client_args is None else client_args
|
||||
self.client = InfluxDBClient(url=url, token=token, org=org, **client_args)
|
||||
|
||||
write_api_args = {} if write_api_args is None else write_api_args
|
||||
self.write_api = self.client.write_api(**write_api_args)
|
||||
|
||||
def __del__(self):
|
||||
"""Make sure all resources are closed."""
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close the write_api, client and logger."""
|
||||
self.write_api.close()
|
||||
self.client.close()
|
||||
super().close()
|
||||
|
||||
def emit(self, record: logging.LogRecord) -> None:
|
||||
"""Emit a record via the influxDB WriteApi."""
|
||||
try:
|
||||
message = self.format(record)
|
||||
extra = self._get_extra_values(record)
|
||||
return self.write_api.write(record=message, **extra)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except (Exception,):
|
||||
self.handleError(record)
|
||||
|
||||
def _get_extra_values(self, record: logging.LogRecord) -> dict:
|
||||
"""
|
||||
Extract all items from the record that were injected via extra.
|
||||
|
||||
Example: `logging.debug(msg, extra={key: value, ...})`.
|
||||
"""
|
||||
extra = {'bucket': self.bucket}
|
||||
extra.update({key: value for key, value in record.__dict__.items()
|
||||
if key not in self.DEFAULT_LOG_RECORD_KEYS})
|
||||
return extra
|
||||
@@ -1,60 +0,0 @@
|
||||
"""
|
||||
An organization is a workspace for a group of users.
|
||||
|
||||
All dashboards, tasks, buckets, members, etc., belong to an organization.
|
||||
"""
|
||||
|
||||
from influxdb_client import OrganizationsService, UsersService, Organization, PatchOrganizationRequest
|
||||
|
||||
|
||||
class OrganizationsApi(object):
|
||||
"""Implementation for '/api/v2/orgs' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._organizations_service = OrganizationsService(influxdb_client.api_client)
|
||||
self._users_service = UsersService(influxdb_client.api_client)
|
||||
|
||||
def me(self):
|
||||
"""Return the current authenticated user."""
|
||||
user = self._users_service.get_me()
|
||||
return user
|
||||
|
||||
def find_organization(self, org_id):
|
||||
"""Retrieve an organization."""
|
||||
return self._organizations_service.get_orgs_id(org_id=org_id)
|
||||
|
||||
def find_organizations(self, **kwargs):
|
||||
"""
|
||||
List all organizations.
|
||||
|
||||
:key int offset: Offset for pagination
|
||||
:key int limit: Limit for pagination
|
||||
:key bool descending:
|
||||
:key str org: Filter organizations to a specific organization name.
|
||||
:key str org_id: Filter organizations to a specific organization ID.
|
||||
:key str user_id: Filter organizations to a specific user ID.
|
||||
"""
|
||||
return self._organizations_service.get_orgs(**kwargs).orgs
|
||||
|
||||
def create_organization(self, name: str = None, organization: Organization = None) -> Organization:
|
||||
"""Create an organization."""
|
||||
if organization is None:
|
||||
organization = Organization(name=name)
|
||||
return self._organizations_service.post_orgs(post_organization_request=organization)
|
||||
|
||||
def update_organization(self, organization: Organization) -> Organization:
|
||||
"""Update an organization.
|
||||
|
||||
:param organization: Organization update to apply (required)
|
||||
:return: Organization
|
||||
"""
|
||||
request = PatchOrganizationRequest(name=organization.name,
|
||||
description=organization.description)
|
||||
|
||||
return self._organizations_service.patch_orgs_id(org_id=organization.id, patch_organization_request=request)
|
||||
|
||||
def delete_organization(self, org_id: str):
|
||||
"""Delete an organization."""
|
||||
return self._organizations_service.delete_orgs_id(org_id=org_id)
|
||||
@@ -1,310 +0,0 @@
|
||||
"""
|
||||
Querying InfluxDB by FluxLang.
|
||||
|
||||
Flux is InfluxData’s functional data scripting language designed for querying, analyzing, and acting on data.
|
||||
"""
|
||||
|
||||
from typing import List, Generator, Any, Callable
|
||||
|
||||
from influxdb_client import Dialect
|
||||
from influxdb_client.client._base import _BaseQueryApi
|
||||
from influxdb_client.client.flux_table import FluxRecord, TableList, CSVIterator
|
||||
|
||||
|
||||
class QueryOptions(object):
|
||||
"""Query options."""
|
||||
|
||||
def __init__(self, profilers: List[str] = None, profiler_callback: Callable = None) -> None:
|
||||
"""
|
||||
Initialize query options.
|
||||
|
||||
:param profilers: list of enabled flux profilers
|
||||
:param profiler_callback: callback function return profilers (FluxRecord)
|
||||
"""
|
||||
self.profilers = profilers
|
||||
self.profiler_callback = profiler_callback
|
||||
|
||||
|
||||
class QueryApi(_BaseQueryApi):
|
||||
"""Implementation for '/api/v2/query' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client, query_options=QueryOptions()):
|
||||
"""
|
||||
Initialize query client.
|
||||
|
||||
:param influxdb_client: influxdb client
|
||||
"""
|
||||
super().__init__(influxdb_client=influxdb_client, query_options=query_options)
|
||||
|
||||
def query_csv(self, query: str, org=None, dialect: Dialect = _BaseQueryApi.default_dialect, params: dict = None) \
|
||||
-> CSVIterator:
|
||||
"""
|
||||
Execute the Flux query and return results as a CSV iterator. Each iteration returns a row of the CSV file.
|
||||
|
||||
:param query: a Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param dialect: csv dialect format
|
||||
:param params: bind parameters
|
||||
:return: :class:`~Iterator[List[str]]` wrapped into :class:`~influxdb_client.client.flux_table.CSVIterator`
|
||||
:rtype: CSVIterator
|
||||
|
||||
Serialization the query results to flattened list of values via :func:`~influxdb_client.client.flux_table.CSVIterator.to_values`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using CSV iterator
|
||||
csv_iterator = client.query_api().query_csv('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to values
|
||||
output = csv_iterator.to_values()
|
||||
print(output)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['#datatype', 'string', 'long', 'dateTime:RFC3339', 'dateTime:RFC3339', 'dateTime:RFC3339', 'double', 'string', 'string', 'string']
|
||||
['#group', 'false', 'false', 'true', 'true', 'false', 'false', 'true', 'true', 'true']
|
||||
['#default', '_result', '', '', '', '', '', '', '', '']
|
||||
['', 'result', 'table', '_start', '_stop', '_time', '_value', '_field', '_measurement', 'location']
|
||||
['', '', '0', '2022-06-16', '2022-06-16', '2022-06-16', '24.3', 'temperature', 'my_measurement', 'New York']
|
||||
['', '', '1', '2022-06-16', '2022-06-16', '2022-06-16', '25.3', 'temperature', 'my_measurement', 'Prague']
|
||||
...
|
||||
]
|
||||
|
||||
If you would like to turn off `Annotated CSV header's <https://docs.influxdata.com/influxdb/latest/reference/syntax/annotated-csv/>`_ you can use following code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient, Dialect
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using CSV iterator
|
||||
csv_iterator = client.query_api().query_csv('from(bucket:"my-bucket") |> range(start: -10m)',
|
||||
dialect=Dialect(header=False, annotations=[]))
|
||||
|
||||
for csv_line in csv_iterator:
|
||||
print(csv_line)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['', '_result', '0', '2022-06-16', '2022-06-16', '2022-06-16', '24.3', 'temperature', 'my_measurement', 'New York']
|
||||
['', '_result', '1', '2022-06-16', '2022-06-16', '2022-06-16', '25.3', 'temperature', 'my_measurement', 'Prague']
|
||||
...
|
||||
]
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
response = self._query_api.post_query(org=org, query=self._create_query(query, dialect, params),
|
||||
async_req=False, _preload_content=False)
|
||||
|
||||
return self._to_csv(response)
|
||||
|
||||
def query_raw(self, query: str, org=None, dialect=_BaseQueryApi.default_dialect, params: dict = None):
|
||||
"""
|
||||
Execute synchronous Flux query and return result as raw unprocessed result as a str.
|
||||
|
||||
:param query: a Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param dialect: csv dialect format
|
||||
:param params: bind parameters
|
||||
:return: str
|
||||
"""
|
||||
org = self._org_param(org)
|
||||
result = self._query_api.post_query(org=org, query=self._create_query(query, dialect, params), async_req=False,
|
||||
_preload_content=False)
|
||||
|
||||
return result
|
||||
|
||||
def query(self, query: str, org=None, params: dict = None) -> TableList:
|
||||
"""Execute synchronous Flux query and return result as a :class:`~influxdb_client.client.flux_table.FluxTable` list.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param params: bind parameters
|
||||
:return: :class:`~influxdb_client.client.flux_table.FluxTable` list wrapped into
|
||||
:class:`~influxdb_client.client.flux_table.TableList`
|
||||
:rtype: TableList
|
||||
|
||||
Serialization the query results to flattened list of values via :func:`~influxdb_client.client.flux_table.TableList.to_values`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to values
|
||||
output = tables.to_values(columns=['location', '_time', '_value'])
|
||||
print(output)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['New York', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 24.3],
|
||||
['Prague', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 25.3],
|
||||
...
|
||||
]
|
||||
|
||||
Serialization the query results to JSON via :func:`~influxdb_client.client.flux_table.TableList.to_json`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to JSON
|
||||
output = tables.to_json(indent=5)
|
||||
print(output)
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:00.897825+00:00",
|
||||
"region": "north",
|
||||
"_field": "usage",
|
||||
"_value": 15
|
||||
},
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:01.897825+00:00",
|
||||
"region": "west",
|
||||
"_field": "usage",
|
||||
"_value": 10
|
||||
},
|
||||
...
|
||||
]
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
|
||||
response = self._query_api.post_query(org=org, query=self._create_query(query, self.default_dialect, params),
|
||||
async_req=False, _preload_content=False, _return_http_data_only=False)
|
||||
|
||||
return self._to_tables(response, query_options=self._get_query_options())
|
||||
|
||||
def query_stream(self, query: str, org=None, params: dict = None) -> Generator['FluxRecord', Any, None]:
|
||||
"""
|
||||
Execute synchronous Flux query and return stream of FluxRecord as a Generator['FluxRecord'].
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param params: bind parameters
|
||||
:return: Generator['FluxRecord']
|
||||
"""
|
||||
org = self._org_param(org)
|
||||
|
||||
response = self._query_api.post_query(org=org, query=self._create_query(query, self.default_dialect, params),
|
||||
async_req=False, _preload_content=False, _return_http_data_only=False)
|
||||
return self._to_flux_record_stream(response, query_options=self._get_query_options())
|
||||
|
||||
def query_data_frame(self, query: str, org=None, data_frame_index: List[str] = None, params: dict = None,
|
||||
use_extension_dtypes: bool = False):
|
||||
"""
|
||||
Execute synchronous Flux query and return Pandas DataFrame.
|
||||
|
||||
.. note:: If the ``query`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param data_frame_index: the list of columns that are used as DataFrame index
|
||||
:param params: bind parameters
|
||||
:param use_extension_dtypes: set to ``True`` to use panda's extension data types.
|
||||
Useful for queries with ``pivot`` function.
|
||||
When data has missing values, column data type may change (to ``object`` or ``float64``).
|
||||
Nullable extension types (``Int64``, ``Float64``, ``boolean``) support ``panda.NA`` value.
|
||||
For more info, see https://pandas.pydata.org/docs/user_guide/missing_data.html.
|
||||
:return: :class:`~DataFrame` or :class:`~List[DataFrame]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
_generator = self.query_data_frame_stream(query, org=org, data_frame_index=data_frame_index, params=params,
|
||||
use_extension_dtypes=use_extension_dtypes)
|
||||
return self._to_data_frames(_generator)
|
||||
|
||||
def query_data_frame_stream(self, query: str, org=None, data_frame_index: List[str] = None, params: dict = None,
|
||||
use_extension_dtypes: bool = False):
|
||||
"""
|
||||
Execute synchronous Flux query and return stream of Pandas DataFrame as a :class:`~Generator[DataFrame]`.
|
||||
|
||||
.. note:: If the ``query`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param data_frame_index: the list of columns that are used as DataFrame index
|
||||
:param params: bind parameters
|
||||
:param use_extension_dtypes: set to ``True`` to use panda's extension data types.
|
||||
Useful for queries with ``pivot`` function.
|
||||
When data has missing values, column data type may change (to ``object`` or ``float64``).
|
||||
Nullable extension types (``Int64``, ``Float64``, ``boolean``) support ``panda.NA`` value.
|
||||
For more info, see https://pandas.pydata.org/docs/user_guide/missing_data.html.
|
||||
:return: :class:`~Generator[DataFrame]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
|
||||
response = self._query_api.post_query(org=org, query=self._create_query(query, self.default_dialect, params,
|
||||
dataframe_query=True),
|
||||
async_req=False, _preload_content=False, _return_http_data_only=False)
|
||||
|
||||
return self._to_data_frame_stream(data_frame_index=data_frame_index,
|
||||
response=response,
|
||||
query_options=self._get_query_options(),
|
||||
use_extension_dtypes=use_extension_dtypes)
|
||||
|
||||
def __del__(self):
|
||||
"""Close QueryAPI."""
|
||||
pass
|
||||
@@ -1,236 +0,0 @@
|
||||
"""
|
||||
Querying InfluxDB by FluxLang.
|
||||
|
||||
Flux is InfluxData’s functional data scripting language designed for querying, analyzing, and acting on data.
|
||||
"""
|
||||
from typing import List, AsyncGenerator
|
||||
|
||||
from influxdb_client.client._base import _BaseQueryApi
|
||||
from influxdb_client.client.flux_table import FluxRecord, TableList
|
||||
from influxdb_client.client.query_api import QueryOptions
|
||||
from influxdb_client.rest import _UTF_8_encoding, ApiException
|
||||
from .._async.rest import RESTResponseAsync
|
||||
|
||||
|
||||
class QueryApiAsync(_BaseQueryApi):
|
||||
"""Asynchronous implementation for '/api/v2/query' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client, query_options=QueryOptions()):
|
||||
"""
|
||||
Initialize query client.
|
||||
|
||||
:param influxdb_client: influxdb client
|
||||
"""
|
||||
super().__init__(influxdb_client=influxdb_client, query_options=query_options)
|
||||
|
||||
async def query(self, query: str, org=None, params: dict = None) -> TableList:
|
||||
"""
|
||||
Execute asynchronous Flux query and return result as a :class:`~influxdb_client.client.flux_table.FluxTable` list.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param params: bind parameters
|
||||
:return: :class:`~influxdb_client.client.flux_table.FluxTable` list wrapped into
|
||||
:class:`~influxdb_client.client.flux_table.TableList`
|
||||
:rtype: TableList
|
||||
|
||||
Serialization the query results to flattened list of values via :func:`~influxdb_client.client.flux_table.TableList.to_values`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
|
||||
async with InfluxDBClientAsync(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
|
||||
# Query: using Table structure
|
||||
tables = await client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to values
|
||||
output = tables.to_values(columns=['location', '_time', '_value'])
|
||||
print(output)
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
[
|
||||
['New York', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 24.3],
|
||||
['Prague', datetime.datetime(2022, 6, 7, 11, 3, 22, 917593, tzinfo=tzutc()), 25.3],
|
||||
...
|
||||
]
|
||||
|
||||
Serialization the query results to JSON via :func:`~influxdb_client.client.flux_table.TableList.to_json`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client.client.influxdb_client_async import InfluxDBClientAsync
|
||||
|
||||
async with InfluxDBClientAsync(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
# Query: using Table structure
|
||||
tables = await client.query_api().query('from(bucket:"my-bucket") |> range(start: -10m)')
|
||||
|
||||
# Serialize to JSON
|
||||
output = tables.to_json(indent=5)
|
||||
print(output)
|
||||
|
||||
.. code-block:: javascript
|
||||
|
||||
[
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:00.897825+00:00",
|
||||
"region": "north",
|
||||
"_field": "usage",
|
||||
"_value": 15
|
||||
},
|
||||
{
|
||||
"_measurement": "mem",
|
||||
"_start": "2021-06-23T06:50:11.897825+00:00",
|
||||
"_stop": "2021-06-25T06:50:11.897825+00:00",
|
||||
"_time": "2020-02-27T16:20:01.897825+00:00",
|
||||
"region": "west",
|
||||
"_field": "usage",
|
||||
"_value": 10
|
||||
},
|
||||
...
|
||||
]
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
|
||||
response = await self._post_query(org=org, query=self._create_query(query, self.default_dialect, params))
|
||||
|
||||
return await self._to_tables_async(response, query_options=self._get_query_options())
|
||||
|
||||
async def query_stream(self, query: str, org=None, params: dict = None) -> AsyncGenerator['FluxRecord', None]:
|
||||
"""
|
||||
Execute asynchronous Flux query and return stream of :class:`~influxdb_client.client.flux_table.FluxRecord` as an AsyncGenerator[:class:`~influxdb_client.client.flux_table.FluxRecord`].
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param params: bind parameters
|
||||
:return: AsyncGenerator[:class:`~influxdb_client.client.flux_table.FluxRecord`]
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
|
||||
response = await self._post_query(org=org, query=self._create_query(query, self.default_dialect, params))
|
||||
|
||||
return await self._to_flux_record_stream_async(response, query_options=self._get_query_options())
|
||||
|
||||
async def query_data_frame(self, query: str, org=None, data_frame_index: List[str] = None, params: dict = None,
|
||||
use_extension_dtypes: bool = False):
|
||||
"""
|
||||
Execute asynchronous Flux query and return :class:`~pandas.core.frame.DataFrame`.
|
||||
|
||||
.. note:: If the ``query`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param data_frame_index: the list of columns that are used as DataFrame index
|
||||
:param params: bind parameters
|
||||
:param use_extension_dtypes: set to ``True`` to use panda's extension data types.
|
||||
Useful for queries with ``pivot`` function.
|
||||
When data has missing values, column data type may change (to ``object`` or ``float64``).
|
||||
Nullable extension types (``Int64``, ``Float64``, ``boolean``) support ``panda.NA`` value.
|
||||
For more info, see https://pandas.pydata.org/docs/user_guide/missing_data.html.
|
||||
:return: :class:`~DataFrame` or :class:`~List[DataFrame]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
_generator = await self.query_data_frame_stream(query, org=org, data_frame_index=data_frame_index,
|
||||
params=params, use_extension_dtypes=use_extension_dtypes)
|
||||
|
||||
dataframes = []
|
||||
async for dataframe in _generator:
|
||||
dataframes.append(dataframe)
|
||||
|
||||
return self._to_data_frames(dataframes)
|
||||
|
||||
async def query_data_frame_stream(self, query: str, org=None, data_frame_index: List[str] = None,
|
||||
params: dict = None, use_extension_dtypes: bool = False):
|
||||
"""
|
||||
Execute asynchronous Flux query and return stream of :class:`~pandas.core.frame.DataFrame` as an AsyncGenerator[:class:`~pandas.core.frame.DataFrame`].
|
||||
|
||||
.. note:: If the ``query`` returns tables with differing schemas than the client generates a :class:`~DataFrame` for each of them.
|
||||
|
||||
:param query: the Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param data_frame_index: the list of columns that are used as DataFrame index
|
||||
:param params: bind parameters
|
||||
:param use_extension_dtypes: set to ``True`` to use panda's extension data types.
|
||||
Useful for queries with ``pivot`` function.
|
||||
When data has missing values, column data type may change (to ``object`` or ``float64``).
|
||||
Nullable extension types (``Int64``, ``Float64``, ``boolean``) support ``panda.NA`` value.
|
||||
For more info, see https://pandas.pydata.org/docs/user_guide/missing_data.html.
|
||||
:return: :class:`AsyncGenerator[:class:`DataFrame`]`
|
||||
|
||||
.. warning:: For the optimal processing of the query results use the ``pivot() function`` which align results as a table.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
from(bucket:"my-bucket")
|
||||
|> range(start: -5m, stop: now())
|
||||
|> filter(fn: (r) => r._measurement == "mem")
|
||||
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
""" # noqa: E501
|
||||
org = self._org_param(org)
|
||||
|
||||
response = await self._post_query(org=org, query=self._create_query(query, self.default_dialect, params,
|
||||
dataframe_query=True))
|
||||
|
||||
return await self._to_data_frame_stream_async(data_frame_index=data_frame_index, response=response,
|
||||
query_options=self._get_query_options(),
|
||||
use_extension_dtypes=use_extension_dtypes)
|
||||
|
||||
async def query_raw(self, query: str, org=None, dialect=_BaseQueryApi.default_dialect, params: dict = None):
|
||||
"""
|
||||
Execute asynchronous Flux query and return result as raw unprocessed result as a str.
|
||||
|
||||
:param query: a Flux query
|
||||
:param str, Organization org: specifies the organization for executing the query;
|
||||
Take the ``ID``, ``Name`` or ``Organization``.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param dialect: csv dialect format
|
||||
:param params: bind parameters
|
||||
:return: :class:`~str`
|
||||
"""
|
||||
org = self._org_param(org)
|
||||
result = await self._post_query(org=org, query=self._create_query(query, dialect, params))
|
||||
raw_bytes = await result.read()
|
||||
return raw_bytes.decode(_UTF_8_encoding)
|
||||
|
||||
async def _post_query(self, org, query):
|
||||
response = await self._query_api.post_query_async(org=org,
|
||||
query=query,
|
||||
async_req=False,
|
||||
_preload_content=False,
|
||||
_return_http_data_only=True)
|
||||
if not 200 <= response.status <= 299:
|
||||
data = await response.read()
|
||||
raise ApiException(http_resp=RESTResponseAsync(response, data))
|
||||
|
||||
return response
|
||||
@@ -1,226 +0,0 @@
|
||||
"""
|
||||
Process and analyze your data with tasks in the InfluxDB task engine.
|
||||
|
||||
Use tasks (scheduled Flux queries) to input a data stream and then analyze, modify, and act on the data accordingly.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from typing import List
|
||||
|
||||
from influxdb_client import TasksService, Task, TaskCreateRequest, TaskUpdateRequest, LabelResponse, LabelMapping, \
|
||||
AddResourceMemberRequestBody, RunManually, Run, LogEvent
|
||||
from influxdb_client.client._pages import _Paginated
|
||||
|
||||
|
||||
class TasksApi(object):
|
||||
"""Implementation for '/api/v2/tasks' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._service = TasksService(influxdb_client.api_client)
|
||||
|
||||
def find_task_by_id(self, task_id) -> Task:
|
||||
"""Retrieve a task."""
|
||||
task = self._service.get_tasks_id(task_id)
|
||||
return task
|
||||
|
||||
def find_tasks(self, **kwargs):
|
||||
"""List all tasks up to set limit (max 500).
|
||||
|
||||
:key str name: only returns tasks with the specified name
|
||||
:key str after: returns tasks after specified ID
|
||||
:key str user: filter tasks to a specific user ID
|
||||
:key str org: filter tasks to a specific organization name
|
||||
:key str org_id: filter tasks to a specific organization ID
|
||||
:key int limit: the number of tasks to return
|
||||
:return: Tasks
|
||||
"""
|
||||
return self._service.get_tasks(**kwargs).tasks
|
||||
|
||||
def find_tasks_iter(self, **kwargs):
|
||||
"""Iterate over all tasks with pagination.
|
||||
|
||||
:key str name: only returns tasks with the specified name
|
||||
:key str after: returns tasks after specified ID
|
||||
:key str user: filter tasks to a specific user ID
|
||||
:key str org: filter tasks to a specific organization name
|
||||
:key str org_id: filter tasks to a specific organization ID
|
||||
:key int limit: the number of tasks in one page
|
||||
:return: Tasks iterator
|
||||
"""
|
||||
return _Paginated(self._service.get_tasks, lambda response: response.tasks).find_iter(**kwargs)
|
||||
|
||||
def create_task(self, task: Task = None, task_create_request: TaskCreateRequest = None) -> Task:
|
||||
"""Create a new task."""
|
||||
if task_create_request is not None:
|
||||
return self._service.post_tasks(task_create_request)
|
||||
|
||||
if task is not None:
|
||||
request = TaskCreateRequest(flux=task.flux, org_id=task.org_id, org=task.org, description=task.description,
|
||||
status=task.status)
|
||||
|
||||
return self.create_task(task_create_request=request)
|
||||
|
||||
raise ValueError("task or task_create_request must be not None")
|
||||
|
||||
@staticmethod
|
||||
def _create_task(name: str, flux: str, every, cron, org_id: str) -> Task:
|
||||
|
||||
task = Task(id=0, name=name, org_id=org_id, status="active", flux=flux)
|
||||
|
||||
repetition = ""
|
||||
if every is not None:
|
||||
repetition += "every: "
|
||||
repetition += every
|
||||
|
||||
if cron is not None:
|
||||
repetition += "cron: "
|
||||
repetition += '"' + cron + '"'
|
||||
|
||||
flux_with_options = '{} \n\noption task = {{name: "{}", {}}}'.format(flux, name, repetition)
|
||||
task.flux = flux_with_options
|
||||
|
||||
return task
|
||||
|
||||
def create_task_every(self, name, flux, every, organization) -> Task:
|
||||
"""Create a new task with every repetition schedule."""
|
||||
task = self._create_task(name, flux, every, None, organization.id)
|
||||
return self.create_task(task)
|
||||
|
||||
def create_task_cron(self, name: str, flux: str, cron: str, org_id: str) -> Task:
|
||||
"""Create a new task with cron repetition schedule."""
|
||||
task = self._create_task(name=name, flux=flux, cron=cron, org_id=org_id, every=None)
|
||||
return self.create_task(task)
|
||||
|
||||
def delete_task(self, task_id: str):
|
||||
"""Delete a task."""
|
||||
if task_id is not None:
|
||||
return self._service.delete_tasks_id(task_id=task_id)
|
||||
|
||||
def update_task(self, task: Task) -> Task:
|
||||
"""Update a task."""
|
||||
req = TaskUpdateRequest(flux=task.flux, description=task.description, every=task.every, cron=task.cron,
|
||||
status=task.status, offset=task.offset)
|
||||
|
||||
return self.update_task_request(task_id=task.id, task_update_request=req)
|
||||
|
||||
def update_task_request(self, task_id, task_update_request: TaskUpdateRequest) -> Task:
|
||||
"""Update a task."""
|
||||
return self._service.patch_tasks_id(task_id=task_id, task_update_request=task_update_request)
|
||||
|
||||
def clone_task(self, task: Task) -> Task:
|
||||
"""Clone a task."""
|
||||
cloned = Task(id=0, name=task.name, org_id=task.org_id, org=task.org, flux=task.flux, status="active")
|
||||
|
||||
created = self.create_task(cloned)
|
||||
if task.id:
|
||||
labels = self.get_labels(task.id)
|
||||
for label in labels.labels:
|
||||
self.add_label(label.id, created.id)
|
||||
return created
|
||||
|
||||
def get_labels(self, task_id):
|
||||
"""List all labels for a task."""
|
||||
return self._service.get_tasks_id_labels(task_id=task_id)
|
||||
|
||||
def add_label(self, label_id: str, task_id: str) -> LabelResponse:
|
||||
"""Add a label to a task."""
|
||||
label_mapping = LabelMapping(label_id=label_id)
|
||||
return self._service.post_tasks_id_labels(task_id=task_id, label_mapping=label_mapping)
|
||||
|
||||
def delete_label(self, label_id: str, task_id: str):
|
||||
"""Delete a label from a task."""
|
||||
return self._service.delete_tasks_id_labels_id(task_id=task_id, label_id=label_id)
|
||||
|
||||
def get_members(self, task_id: str):
|
||||
"""List all task members."""
|
||||
return self._service.get_tasks_id_members(task_id=task_id).users
|
||||
|
||||
def add_member(self, member_id, task_id):
|
||||
"""Add a member to a task."""
|
||||
user = AddResourceMemberRequestBody(id=member_id)
|
||||
return self._service.post_tasks_id_members(task_id=task_id, add_resource_member_request_body=user)
|
||||
|
||||
def delete_member(self, member_id, task_id):
|
||||
"""Remove a member from a task."""
|
||||
return self._service.delete_tasks_id_members_id(user_id=member_id, task_id=task_id)
|
||||
|
||||
def get_owners(self, task_id):
|
||||
"""List all owners of a task."""
|
||||
return self._service.get_tasks_id_owners(task_id=task_id).users
|
||||
|
||||
def add_owner(self, owner_id, task_id):
|
||||
"""Add an owner to a task."""
|
||||
user = AddResourceMemberRequestBody(id=owner_id)
|
||||
return self._service.post_tasks_id_owners(task_id=task_id, add_resource_member_request_body=user)
|
||||
|
||||
def delete_owner(self, owner_id, task_id):
|
||||
"""Remove an owner from a task."""
|
||||
return self._service.delete_tasks_id_owners_id(user_id=owner_id, task_id=task_id)
|
||||
|
||||
def get_runs(self, task_id, **kwargs) -> List['Run']:
|
||||
"""
|
||||
Retrieve list of run records for a task.
|
||||
|
||||
:param task_id: task id
|
||||
:key str after: returns runs after specified ID
|
||||
:key int limit: the number of runs to return
|
||||
:key datetime after_time: filter runs to those scheduled after this time, RFC3339
|
||||
:key datetime before_time: filter runs to those scheduled before this time, RFC3339
|
||||
"""
|
||||
return self._service.get_tasks_id_runs(task_id=task_id, **kwargs).runs
|
||||
|
||||
def get_run(self, task_id: str, run_id: str) -> Run:
|
||||
"""
|
||||
Get run record for specific task and run id.
|
||||
|
||||
:param task_id: task id
|
||||
:param run_id: run id
|
||||
:return: Run for specified task and run id
|
||||
"""
|
||||
return self._service.get_tasks_id_runs_id(task_id=task_id, run_id=run_id)
|
||||
|
||||
def get_run_logs(self, task_id: str, run_id: str) -> List['LogEvent']:
|
||||
"""Retrieve all logs for a run."""
|
||||
return self._service.get_tasks_id_runs_id_logs(task_id=task_id, run_id=run_id).events
|
||||
|
||||
def run_manually(self, task_id: str, scheduled_for: datetime = None):
|
||||
"""
|
||||
Manually start a run of the task now overriding the current schedule.
|
||||
|
||||
:param task_id:
|
||||
:param scheduled_for: planned execution
|
||||
"""
|
||||
r = RunManually(scheduled_for=scheduled_for)
|
||||
return self._service.post_tasks_id_runs(task_id=task_id, run_manually=r)
|
||||
|
||||
def retry_run(self, task_id: str, run_id: str):
|
||||
"""
|
||||
Retry a task run.
|
||||
|
||||
:param task_id: task id
|
||||
:param run_id: run id
|
||||
"""
|
||||
return self._service.post_tasks_id_runs_id_retry(task_id=task_id, run_id=run_id)
|
||||
|
||||
def cancel_run(self, task_id: str, run_id: str):
|
||||
"""
|
||||
Cancel a currently running run.
|
||||
|
||||
:param task_id:
|
||||
:param run_id:
|
||||
"""
|
||||
return self._service.delete_tasks_id_runs_id(task_id=task_id, run_id=run_id)
|
||||
|
||||
def get_logs(self, task_id: str) -> List['LogEvent']:
|
||||
"""
|
||||
Retrieve all logs for a task.
|
||||
|
||||
:param task_id: task id
|
||||
"""
|
||||
return self._service.get_tasks_id_logs(task_id=task_id).events
|
||||
|
||||
def find_tasks_by_user(self, task_user_id):
|
||||
"""List all tasks by user."""
|
||||
return self.find_tasks(user=task_user_id)
|
||||
@@ -1,80 +0,0 @@
|
||||
"""
|
||||
Users are those with access to InfluxDB.
|
||||
|
||||
To grant a user permission to access data, add them as a member of an organization
|
||||
and provide them with an authentication token.
|
||||
"""
|
||||
|
||||
from typing import Union
|
||||
from influxdb_client import UsersService, User, Users, UserResponse, PasswordResetBody
|
||||
|
||||
|
||||
class UsersApi(object):
|
||||
"""Implementation for '/api/v2/users' endpoint."""
|
||||
|
||||
def __init__(self, influxdb_client):
|
||||
"""Initialize defaults."""
|
||||
self._influxdb_client = influxdb_client
|
||||
self._service = UsersService(influxdb_client.api_client)
|
||||
|
||||
def me(self) -> User:
|
||||
"""Return the current authenticated user."""
|
||||
user = self._service.get_me()
|
||||
return user
|
||||
|
||||
def create_user(self, name: str) -> User:
|
||||
"""Create a user."""
|
||||
user = User(name=name)
|
||||
|
||||
return self._service.post_users(user=user)
|
||||
|
||||
def update_user(self, user: User) -> UserResponse:
|
||||
"""Update a user.
|
||||
|
||||
:param user: User update to apply (required)
|
||||
:return: User
|
||||
"""
|
||||
return self._service.patch_users_id(user_id=user.id, user=user)
|
||||
|
||||
def update_password(self, user: Union[str, User, UserResponse], password: str) -> None:
|
||||
"""Update a password.
|
||||
|
||||
:param user: User to update password (required)
|
||||
:param password: New password (required)
|
||||
:return: None
|
||||
"""
|
||||
user_id = self._user_id(user)
|
||||
|
||||
return self._service.post_users_id_password(user_id=user_id, password_reset_body=PasswordResetBody(password))
|
||||
|
||||
def delete_user(self, user: Union[str, User, UserResponse]) -> None:
|
||||
"""Delete a user.
|
||||
|
||||
:param user: user id or User
|
||||
:return: None
|
||||
"""
|
||||
user_id = self._user_id(user)
|
||||
|
||||
return self._service.delete_users_id(user_id=user_id)
|
||||
|
||||
def find_users(self, **kwargs) -> Users:
|
||||
"""List all users.
|
||||
|
||||
:key int offset: The offset for pagination. The number of records to skip.
|
||||
:key int limit: Limits the number of records returned. Default is `20`.
|
||||
:key str after: The last resource ID from which to seek from (but not including).
|
||||
This is to be used instead of `offset`.
|
||||
:key str name: The user name.
|
||||
:key str id: The user ID.
|
||||
:return: Buckets
|
||||
"""
|
||||
return self._service.get_users(**kwargs)
|
||||
|
||||
def _user_id(self, user: Union[str, User, UserResponse]):
|
||||
if isinstance(user, User):
|
||||
user_id = user.id
|
||||
elif isinstance(user, UserResponse):
|
||||
user_id = user.id
|
||||
else:
|
||||
user_id = user
|
||||
return user_id
|
||||
@@ -1 +0,0 @@
|
||||
"""Utils package."""
|
||||
@@ -1,101 +0,0 @@
|
||||
"""Utils to get right Date parsing function."""
|
||||
import datetime
|
||||
from sys import version_info
|
||||
import threading
|
||||
from datetime import timezone as tz
|
||||
|
||||
from dateutil import parser
|
||||
|
||||
date_helper = None
|
||||
|
||||
lock_ = threading.Lock()
|
||||
|
||||
|
||||
class DateHelper:
|
||||
"""
|
||||
DateHelper to groups different implementations of date operations.
|
||||
|
||||
If you would like to serialize the query results to custom timezone, you can use following code:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client.client.util import date_utils
|
||||
from influxdb_client.client.util.date_utils import DateHelper
|
||||
import dateutil.parser
|
||||
from dateutil import tz
|
||||
|
||||
def parse_date(date_string: str):
|
||||
return dateutil.parser.parse(date_string).astimezone(tz.gettz('ETC/GMT+2'))
|
||||
|
||||
date_utils.date_helper = DateHelper()
|
||||
date_utils.date_helper.parse_date = parse_date
|
||||
"""
|
||||
|
||||
def __init__(self, timezone: datetime.tzinfo = tz.utc) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param timezone: Default timezone used for serialization "datetime" without "tzinfo".
|
||||
Default value is "UTC".
|
||||
"""
|
||||
self.timezone = timezone
|
||||
|
||||
def parse_date(self, date_string: str):
|
||||
"""
|
||||
Parse string into Date or Timestamp.
|
||||
|
||||
:return: Returns a :class:`datetime.datetime` object or compliant implementation
|
||||
like :class:`class 'pandas._libs.tslibs.timestamps.Timestamp`
|
||||
"""
|
||||
pass
|
||||
|
||||
def to_nanoseconds(self, delta):
|
||||
"""
|
||||
Get number of nanoseconds in timedelta.
|
||||
|
||||
Solution comes from v1 client. Thx.
|
||||
https://github.com/influxdata/influxdb-python/pull/811
|
||||
"""
|
||||
nanoseconds_in_days = delta.days * 86400 * 10 ** 9
|
||||
nanoseconds_in_seconds = delta.seconds * 10 ** 9
|
||||
nanoseconds_in_micros = delta.microseconds * 10 ** 3
|
||||
|
||||
return nanoseconds_in_days + nanoseconds_in_seconds + nanoseconds_in_micros
|
||||
|
||||
def to_utc(self, value: datetime):
|
||||
"""
|
||||
Convert datetime to UTC timezone.
|
||||
|
||||
:param value: datetime
|
||||
:return: datetime in UTC
|
||||
"""
|
||||
if not value.tzinfo:
|
||||
return self.to_utc(value.replace(tzinfo=self.timezone))
|
||||
else:
|
||||
return value.astimezone(tz.utc)
|
||||
|
||||
|
||||
def get_date_helper() -> DateHelper:
|
||||
"""
|
||||
Return DateHelper with proper implementation.
|
||||
|
||||
If there is a 'ciso8601' than use 'ciso8601.parse_datetime' else
|
||||
use 'datetime.fromisoformat' (Python >= 3.11) or 'dateutil.parse' (Python < 3.11).
|
||||
"""
|
||||
global date_helper
|
||||
if date_helper is None:
|
||||
with lock_:
|
||||
# avoid duplicate initialization
|
||||
if date_helper is None:
|
||||
_date_helper = DateHelper()
|
||||
try:
|
||||
import ciso8601
|
||||
_date_helper.parse_date = ciso8601.parse_datetime
|
||||
except ModuleNotFoundError:
|
||||
if (version_info.major, version_info.minor) >= (3, 11):
|
||||
_date_helper.parse_date = datetime.datetime.fromisoformat
|
||||
else:
|
||||
_date_helper.parse_date = parser.parse
|
||||
date_helper = _date_helper
|
||||
|
||||
return date_helper
|
||||
@@ -1,15 +0,0 @@
|
||||
"""Pandas date utils."""
|
||||
from influxdb_client.client.util.date_utils import DateHelper
|
||||
from influxdb_client.extras import pd
|
||||
|
||||
|
||||
class PandasDateTimeHelper(DateHelper):
|
||||
"""DateHelper that use Pandas library with nanosecond precision."""
|
||||
|
||||
def parse_date(self, date_string: str):
|
||||
"""Parse date string into `class 'pandas._libs.tslibs.timestamps.Timestamp`."""
|
||||
return pd.to_datetime(date_string)
|
||||
|
||||
def to_nanoseconds(self, delta):
|
||||
"""Get number of nanoseconds with nanos precision."""
|
||||
return super().to_nanoseconds(delta) + (delta.nanoseconds if hasattr(delta, 'nanoseconds') else 0)
|
||||
@@ -1,50 +0,0 @@
|
||||
"""Functions to share utility across client classes."""
|
||||
from influxdb_client.rest import ApiException
|
||||
|
||||
|
||||
def _is_id(value):
|
||||
"""
|
||||
Check if the value is valid InfluxDB ID.
|
||||
|
||||
:param value: to check
|
||||
:return: True if provided parameter is valid InfluxDB ID.
|
||||
"""
|
||||
if value and len(value) == 16:
|
||||
try:
|
||||
int(value, 16)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
def get_org_query_param(org, client, required_id=False):
|
||||
"""
|
||||
Get required type of Org query parameter.
|
||||
|
||||
:param str, Organization org: value provided as a parameter into API (optional)
|
||||
:param InfluxDBClient client: with default value for Org parameter
|
||||
:param bool required_id: true if the query param has to be a ID
|
||||
:return: request type of org query parameter or None
|
||||
"""
|
||||
_org = client.org if org is None else org
|
||||
if 'Organization' in type(_org).__name__:
|
||||
_org = _org.id
|
||||
if required_id and _org and not _is_id(_org):
|
||||
try:
|
||||
organizations = client.organizations_api().find_organizations(org=_org)
|
||||
if len(organizations) < 1:
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
message = f"The client cannot find organization with name: '{_org}' " \
|
||||
"to determine their ID. Are you using token with sufficient permission?"
|
||||
raise InfluxDBError(response=None, message=message)
|
||||
return organizations[0].id
|
||||
except ApiException as e:
|
||||
if e.status == 404:
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
message = f"The client cannot find organization with name: '{_org}' " \
|
||||
"to determine their ID."
|
||||
raise InfluxDBError(response=None, message=message)
|
||||
raise e
|
||||
|
||||
return _org
|
||||
@@ -1,205 +0,0 @@
|
||||
"""
|
||||
Helpers classes to make easier use the client in multiprocessing environment.
|
||||
|
||||
For more information how the multiprocessing works see Python's
|
||||
`reference docs <https://docs.python.org/3/library/multiprocessing.html>`_.
|
||||
"""
|
||||
import logging
|
||||
import multiprocessing
|
||||
|
||||
from influxdb_client import InfluxDBClient, WriteOptions
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.util.multiprocessing_helper')
|
||||
|
||||
|
||||
def _success_callback(conf: (str, str, str), data: str):
|
||||
"""Successfully writen batch."""
|
||||
logger.debug(f"Written batch: {conf}, data: {data}")
|
||||
|
||||
|
||||
def _error_callback(conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
"""Unsuccessfully writen batch."""
|
||||
logger.debug(f"Cannot write batch: {conf}, data: {data} due: {exception}")
|
||||
|
||||
|
||||
def _retry_callback(conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
"""Retryable error."""
|
||||
logger.debug(f"Retryable error occurs for batch: {conf}, data: {data} retry: {exception}")
|
||||
|
||||
|
||||
class _PoisonPill:
|
||||
"""To notify process to terminate."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MultiprocessingWriter(multiprocessing.Process):
|
||||
"""
|
||||
The Helper class to write data into InfluxDB in independent OS process.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import WriteOptions
|
||||
from influxdb_client.client.util.multiprocessing_helper import MultiprocessingWriter
|
||||
|
||||
|
||||
def main():
|
||||
writer = MultiprocessingWriter(url="http://localhost:8086", token="my-token", org="my-org",
|
||||
write_options=WriteOptions(batch_size=100))
|
||||
writer.start()
|
||||
|
||||
for x in range(1, 1000):
|
||||
writer.write(bucket="my-bucket", record=f"mem,tag=a value={x}i {x}")
|
||||
|
||||
writer.__del__()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
How to use with context_manager:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import WriteOptions
|
||||
from influxdb_client.client.util.multiprocessing_helper import MultiprocessingWriter
|
||||
|
||||
|
||||
def main():
|
||||
with MultiprocessingWriter(url="http://localhost:8086", token="my-token", org="my-org",
|
||||
write_options=WriteOptions(batch_size=100)) as writer:
|
||||
for x in range(1, 1000):
|
||||
writer.write(bucket="my-bucket", record=f"mem,tag=a value={x}i {x}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
How to handle batch events:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import WriteOptions
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
from influxdb_client.client.util.multiprocessing_helper import MultiprocessingWriter
|
||||
|
||||
|
||||
class BatchingCallback(object):
|
||||
|
||||
def success(self, conf: (str, str, str), data: str):
|
||||
print(f"Written batch: {conf}, data: {data}")
|
||||
|
||||
def error(self, conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
print(f"Cannot write batch: {conf}, data: {data} due: {exception}")
|
||||
|
||||
def retry(self, conf: (str, str, str), data: str, exception: InfluxDBError):
|
||||
print(f"Retryable error occurs for batch: {conf}, data: {data} retry: {exception}")
|
||||
|
||||
|
||||
def main():
|
||||
callback = BatchingCallback()
|
||||
with MultiprocessingWriter(url="http://localhost:8086", token="my-token", org="my-org",
|
||||
success_callback=callback.success,
|
||||
error_callback=callback.error,
|
||||
retry_callback=callback.retry) as writer:
|
||||
|
||||
for x in range(1, 1000):
|
||||
writer.write(bucket="my-bucket", record=f"mem,tag=a value={x}i {x}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
"""
|
||||
|
||||
__started__ = False
|
||||
__disposed__ = False
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
For more information how to initialize the writer see the examples above.
|
||||
|
||||
:param kwargs: arguments are passed into ``__init__`` function of ``InfluxDBClient`` and ``write_api``.
|
||||
"""
|
||||
multiprocessing.Process.__init__(self)
|
||||
self.kwargs = kwargs
|
||||
self.client = None
|
||||
self.write_api = None
|
||||
self.queue_ = multiprocessing.Manager().Queue()
|
||||
|
||||
def write(self, **kwargs) -> None:
|
||||
"""
|
||||
Append time-series data into underlying queue.
|
||||
|
||||
For more information how to pass arguments see the examples above.
|
||||
|
||||
:param kwargs: arguments are passed into ``write`` function of ``WriteApi``
|
||||
:return: None
|
||||
"""
|
||||
assert self.__disposed__ is False, 'Cannot write data: the writer is closed.'
|
||||
assert self.__started__ is True, 'Cannot write data: the writer is not started.'
|
||||
self.queue_.put(kwargs)
|
||||
|
||||
def run(self):
|
||||
"""Initialize ``InfluxDBClient`` and waits for data to writes into InfluxDB."""
|
||||
# Initialize Client and Write API
|
||||
self.client = InfluxDBClient(**self.kwargs)
|
||||
self.write_api = self.client.write_api(write_options=self.kwargs.get('write_options', WriteOptions()),
|
||||
success_callback=self.kwargs.get('success_callback', _success_callback),
|
||||
error_callback=self.kwargs.get('error_callback', _error_callback),
|
||||
retry_callback=self.kwargs.get('retry_callback', _retry_callback))
|
||||
# Infinite loop - until poison pill
|
||||
while True:
|
||||
next_record = self.queue_.get()
|
||||
if type(next_record) is _PoisonPill:
|
||||
# Poison pill means break the loop
|
||||
self.terminate()
|
||||
self.queue_.task_done()
|
||||
break
|
||||
self.write_api.write(**next_record)
|
||||
self.queue_.task_done()
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start independent process for writing data into InfluxDB."""
|
||||
super().start()
|
||||
self.__started__ = True
|
||||
|
||||
def terminate(self) -> None:
|
||||
"""
|
||||
Cleanup resources in independent process.
|
||||
|
||||
This function **cannot be used** to terminate the ``MultiprocessingWriter``.
|
||||
If you want to finish your writes please call: ``__del__``.
|
||||
"""
|
||||
if self.write_api:
|
||||
logger.info("flushing data...")
|
||||
self.write_api.__del__()
|
||||
self.write_api = None
|
||||
if self.client:
|
||||
self.client.__del__()
|
||||
self.client = None
|
||||
logger.info("closed")
|
||||
|
||||
def __enter__(self):
|
||||
"""Enter the runtime context related to this object."""
|
||||
self.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
"""Exit the runtime context related to this object."""
|
||||
self.__del__()
|
||||
|
||||
def __del__(self):
|
||||
"""Dispose the client and write_api."""
|
||||
if self.__started__:
|
||||
self.queue_.put(_PoisonPill())
|
||||
self.queue_.join()
|
||||
self.join()
|
||||
self.queue_ = None
|
||||
self.__started__ = False
|
||||
self.__disposed__ = True
|
||||
@@ -1,52 +0,0 @@
|
||||
"""The warnings message definition."""
|
||||
import warnings
|
||||
|
||||
|
||||
class MissingPivotFunction(UserWarning):
|
||||
"""User warning about missing pivot() function."""
|
||||
|
||||
@staticmethod
|
||||
def print_warning(query: str):
|
||||
"""Print warning about missing pivot() function and how to deal with that."""
|
||||
if 'fieldsAsCols' in query or 'pivot' in query:
|
||||
return
|
||||
|
||||
message = f"""The query doesn't contains the pivot() function.
|
||||
|
||||
The result will not be shaped to optimal processing by pandas.DataFrame. Use the pivot() function by:
|
||||
|
||||
{query} |> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|
||||
|
||||
You can disable this warning by:
|
||||
import warnings
|
||||
from influxdb_client.client.warnings import MissingPivotFunction
|
||||
|
||||
warnings.simplefilter("ignore", MissingPivotFunction)
|
||||
|
||||
For more info see:
|
||||
- https://docs.influxdata.com/resources/videos/pivots-in-flux/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/universe/pivot/
|
||||
- https://docs.influxdata.com/flux/latest/stdlib/influxdata/influxdb/schema/fieldsascols/
|
||||
"""
|
||||
warnings.warn(message, MissingPivotFunction)
|
||||
|
||||
|
||||
class CloudOnlyWarning(UserWarning):
|
||||
"""User warning about availability only on the InfluxDB Cloud."""
|
||||
|
||||
@staticmethod
|
||||
def print_warning(api_name: str, doc_url: str):
|
||||
"""Print warning about availability only on the InfluxDB Cloud."""
|
||||
message = f"""The '{api_name}' is available only on the InfluxDB Cloud.
|
||||
|
||||
For more info see:
|
||||
- {doc_url}
|
||||
- https://docs.influxdata.com/influxdb/cloud/
|
||||
|
||||
You can disable this warning by:
|
||||
import warnings
|
||||
from influxdb_client.client.warnings import CloudOnlyWarning
|
||||
|
||||
warnings.simplefilter("ignore", CloudOnlyWarning)
|
||||
"""
|
||||
warnings.warn(message, CloudOnlyWarning)
|
||||
@@ -1,56 +0,0 @@
|
||||
# flake8: noqa
|
||||
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# import apis into api package
|
||||
from influxdb_client.service.authorizations_service import AuthorizationsService
|
||||
from influxdb_client.service.backup_service import BackupService
|
||||
from influxdb_client.service.bucket_schemas_service import BucketSchemasService
|
||||
from influxdb_client.service.buckets_service import BucketsService
|
||||
from influxdb_client.service.cells_service import CellsService
|
||||
from influxdb_client.service.checks_service import ChecksService
|
||||
from influxdb_client.service.config_service import ConfigService
|
||||
from influxdb_client.service.dbr_ps_service import DBRPsService
|
||||
from influxdb_client.service.dashboards_service import DashboardsService
|
||||
from influxdb_client.service.delete_service import DeleteService
|
||||
from influxdb_client.service.health_service import HealthService
|
||||
from influxdb_client.service.invokable_scripts_service import InvokableScriptsService
|
||||
from influxdb_client.service.labels_service import LabelsService
|
||||
from influxdb_client.service.legacy_authorizations_service import LegacyAuthorizationsService
|
||||
from influxdb_client.service.metrics_service import MetricsService
|
||||
from influxdb_client.service.notification_endpoints_service import NotificationEndpointsService
|
||||
from influxdb_client.service.notification_rules_service import NotificationRulesService
|
||||
from influxdb_client.service.organizations_service import OrganizationsService
|
||||
from influxdb_client.service.ping_service import PingService
|
||||
from influxdb_client.service.query_service import QueryService
|
||||
from influxdb_client.service.ready_service import ReadyService
|
||||
from influxdb_client.service.remote_connections_service import RemoteConnectionsService
|
||||
from influxdb_client.service.replications_service import ReplicationsService
|
||||
from influxdb_client.service.resources_service import ResourcesService
|
||||
from influxdb_client.service.restore_service import RestoreService
|
||||
from influxdb_client.service.routes_service import RoutesService
|
||||
from influxdb_client.service.rules_service import RulesService
|
||||
from influxdb_client.service.scraper_targets_service import ScraperTargetsService
|
||||
from influxdb_client.service.secrets_service import SecretsService
|
||||
from influxdb_client.service.setup_service import SetupService
|
||||
from influxdb_client.service.signin_service import SigninService
|
||||
from influxdb_client.service.signout_service import SignoutService
|
||||
from influxdb_client.service.sources_service import SourcesService
|
||||
from influxdb_client.service.tasks_service import TasksService
|
||||
from influxdb_client.service.telegraf_plugins_service import TelegrafPluginsService
|
||||
from influxdb_client.service.telegrafs_service import TelegrafsService
|
||||
from influxdb_client.service.templates_service import TemplatesService
|
||||
from influxdb_client.service.users_service import UsersService
|
||||
from influxdb_client.service.variables_service import VariablesService
|
||||
from influxdb_client.service.views_service import ViewsService
|
||||
from influxdb_client.service.write_service import WriteService
|
||||
@@ -1,290 +0,0 @@
|
||||
"""
|
||||
Functions for serialize Pandas DataFrame.
|
||||
|
||||
Much of the code here is inspired by that in the aioinflux packet found here: https://github.com/gusutabopb/aioinflux
|
||||
"""
|
||||
|
||||
import logging
|
||||
import math
|
||||
import re
|
||||
|
||||
from influxdb_client import WritePrecision
|
||||
from influxdb_client.client.write.point import _ESCAPE_KEY, _ESCAPE_STRING, _ESCAPE_MEASUREMENT, DEFAULT_WRITE_PRECISION
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.write.dataframe_serializer')
|
||||
|
||||
|
||||
def _itertuples(data_frame):
|
||||
cols = [data_frame.iloc[:, k] for k in range(len(data_frame.columns))]
|
||||
return zip(data_frame.index, *cols)
|
||||
|
||||
|
||||
class DataframeSerializer:
|
||||
"""Serialize DataFrame into LineProtocols."""
|
||||
|
||||
def __init__(self, data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, chunk_size: int = None,
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Init serializer.
|
||||
|
||||
:param data_frame: Pandas DataFrame to serialize
|
||||
:param point_settings: Default Tags
|
||||
:param precision: The precision for the unix timestamps within the body line-protocol.
|
||||
:param chunk_size: The size of chunk for serializing into chunks.
|
||||
:key data_frame_measurement_name: name of measurement for writing Pandas DataFrame
|
||||
:key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
|
||||
:key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
|
||||
formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
|
||||
or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
|
||||
:key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
|
||||
""" # noqa: E501
|
||||
# This function is hard to understand but for good reason:
|
||||
# the approach used here is considerably more efficient
|
||||
# than the alternatives.
|
||||
#
|
||||
# We build up a Python expression that efficiently converts a data point
|
||||
# tuple into line-protocol entry, and then evaluate the expression
|
||||
# as a lambda so that we can call it. This avoids the overhead of
|
||||
# invoking a function on every data value - we only have one function
|
||||
# call per row instead. The expression consists of exactly
|
||||
# one f-string, so we build up the parts of it as segments
|
||||
# that are concatenated together to make the full f-string inside
|
||||
# the lambda.
|
||||
#
|
||||
# Things are made a little more complex because fields and tags with NaN
|
||||
# values and empty tags are omitted from the generated line-protocol
|
||||
# output.
|
||||
#
|
||||
# As an example, say we have a data frame with two value columns:
|
||||
# a float
|
||||
# b int
|
||||
#
|
||||
# This will generate a lambda expression to be evaluated that looks like
|
||||
# this:
|
||||
#
|
||||
# lambda p: f"""{measurement_name} {keys[0]}={p[1]},{keys[1]}={p[2]}i {p[0].value}"""
|
||||
#
|
||||
# This lambda is then executed for each row p.
|
||||
#
|
||||
# When NaNs are present, the expression looks like this (split
|
||||
# across two lines to satisfy the code-style checker)
|
||||
#
|
||||
# lambda p: f"""{measurement_name} {"" if pd.isna(p[1])
|
||||
# else f"{keys[0]}={p[1]}"},{keys[1]}={p[2]}i {p[0].value}"""
|
||||
#
|
||||
# When there's a NaN value in column a, we'll end up with a comma at the start of the
|
||||
# fields, so we run a regexp substitution after generating the line-protocol entries
|
||||
# to remove this.
|
||||
#
|
||||
# We're careful to run these potentially costly extra steps only when NaN values actually
|
||||
# exist in the data.
|
||||
|
||||
from ...extras import pd, np
|
||||
if not isinstance(data_frame, pd.DataFrame):
|
||||
raise TypeError('Must be DataFrame, but type was: {0}.'
|
||||
.format(type(data_frame)))
|
||||
|
||||
data_frame_measurement_name = kwargs.get('data_frame_measurement_name')
|
||||
if data_frame_measurement_name is None:
|
||||
raise TypeError('"data_frame_measurement_name" is a Required Argument')
|
||||
|
||||
timestamp_column = kwargs.get('data_frame_timestamp_column', None)
|
||||
timestamp_timezone = kwargs.get('data_frame_timestamp_timezone', None)
|
||||
data_frame = data_frame.copy(deep=False)
|
||||
data_frame_timestamp = data_frame.index if timestamp_column is None else data_frame[timestamp_column]
|
||||
if isinstance(data_frame_timestamp, pd.PeriodIndex):
|
||||
data_frame_timestamp = data_frame_timestamp.to_timestamp()
|
||||
else:
|
||||
# TODO: this is almost certainly not what you want
|
||||
# when the index is the default RangeIndex.
|
||||
# Instead, it would probably be better to leave
|
||||
# out the timestamp unless a time column is explicitly
|
||||
# enabled.
|
||||
data_frame_timestamp = pd.to_datetime(data_frame_timestamp, unit=precision)
|
||||
|
||||
if timestamp_timezone:
|
||||
if isinstance(data_frame_timestamp, pd.DatetimeIndex):
|
||||
data_frame_timestamp = data_frame_timestamp.tz_localize(timestamp_timezone)
|
||||
else:
|
||||
data_frame_timestamp = data_frame_timestamp.dt.tz_localize(timestamp_timezone)
|
||||
|
||||
if hasattr(data_frame_timestamp, 'tzinfo') and data_frame_timestamp.tzinfo is None:
|
||||
data_frame_timestamp = data_frame_timestamp.tz_localize('UTC')
|
||||
if timestamp_column is None:
|
||||
data_frame.index = data_frame_timestamp
|
||||
else:
|
||||
data_frame[timestamp_column] = data_frame_timestamp
|
||||
|
||||
data_frame_tag_columns = kwargs.get('data_frame_tag_columns')
|
||||
data_frame_tag_columns = set(data_frame_tag_columns or [])
|
||||
|
||||
# keys holds a list of string keys.
|
||||
keys = []
|
||||
# tags holds a list of tag f-string segments ordered alphabetically by tag key.
|
||||
tags = []
|
||||
# fields holds a list of field f-string segments ordered alphebetically by field key
|
||||
fields = []
|
||||
# field_indexes holds the index into each row of all the fields.
|
||||
field_indexes = []
|
||||
|
||||
if point_settings.defaultTags:
|
||||
for key, value in point_settings.defaultTags.items():
|
||||
# Avoid overwriting existing data if there's a column
|
||||
# that already exists with the default tag's name.
|
||||
# Note: when a new column is added, the old DataFrame
|
||||
# that we've made a shallow copy of is unaffected.
|
||||
# TODO: when there are NaN or empty values in
|
||||
# the column, we could make a deep copy of the
|
||||
# data and fill in those values with the default tag value.
|
||||
if key not in data_frame.columns:
|
||||
data_frame[key] = value
|
||||
data_frame_tag_columns.add(key)
|
||||
|
||||
# Get a list of all the columns sorted by field/tag key.
|
||||
# We want to iterate through the columns in sorted order
|
||||
# so that we know when we're on the first field so we
|
||||
# can know whether a comma is needed for that
|
||||
# field.
|
||||
columns = sorted(enumerate(data_frame.dtypes.items()), key=lambda col: col[1][0])
|
||||
|
||||
# null_columns has a bool value for each column holding
|
||||
# whether that column contains any null (NaN or None) values.
|
||||
null_columns = data_frame.isnull().any()
|
||||
timestamp_index = 0
|
||||
|
||||
# Iterate through the columns building up the expression for each column.
|
||||
for index, (key, value) in columns:
|
||||
key = str(key)
|
||||
key_format = f'{{keys[{len(keys)}]}}'
|
||||
keys.append(key.translate(_ESCAPE_KEY))
|
||||
# The field index is one more than the column index because the
|
||||
# time index is at column zero in the finally zipped-together
|
||||
# result columns.
|
||||
field_index = index + 1
|
||||
val_format = f'p[{field_index}]'
|
||||
|
||||
if key in data_frame_tag_columns:
|
||||
# This column is a tag column.
|
||||
if null_columns.iloc[index]:
|
||||
key_value = f"""{{
|
||||
'' if {val_format} == '' or pd.isna({val_format}) else
|
||||
f',{key_format}={{str({val_format}).translate(_ESCAPE_STRING)}}'
|
||||
}}"""
|
||||
else:
|
||||
key_value = f',{key_format}={{str({val_format}).translate(_ESCAPE_KEY)}}'
|
||||
tags.append(key_value)
|
||||
continue
|
||||
elif timestamp_column is not None and key in timestamp_column:
|
||||
timestamp_index = field_index
|
||||
continue
|
||||
|
||||
# This column is a field column.
|
||||
# Note: no comma separator is needed for the first field.
|
||||
# It's important to omit it because when the first
|
||||
# field column has no nulls, we don't run the comma-removal
|
||||
# regexp substitution step.
|
||||
sep = '' if len(field_indexes) == 0 else ','
|
||||
if issubclass(value.type, np.integer) or issubclass(value.type, np.floating) or issubclass(value.type, np.bool_): # noqa: E501
|
||||
suffix = 'i' if issubclass(value.type, np.integer) else ''
|
||||
if null_columns.iloc[index]:
|
||||
field_value = f"""{{"" if pd.isna({val_format}) else f"{sep}{key_format}={{{val_format}}}{suffix}"}}""" # noqa: E501
|
||||
else:
|
||||
field_value = f"{sep}{key_format}={{{val_format}}}{suffix}"
|
||||
else:
|
||||
if null_columns.iloc[index]:
|
||||
field_value = f"""{{
|
||||
'' if pd.isna({val_format}) else
|
||||
f'{sep}{key_format}="{{str({val_format}).translate(_ESCAPE_STRING)}}"'
|
||||
}}"""
|
||||
else:
|
||||
field_value = f'''{sep}{key_format}="{{str({val_format}).translate(_ESCAPE_STRING)}}"'''
|
||||
field_indexes.append(field_index)
|
||||
fields.append(field_value)
|
||||
|
||||
measurement_name = str(data_frame_measurement_name).translate(_ESCAPE_MEASUREMENT)
|
||||
|
||||
tags = ''.join(tags)
|
||||
fields = ''.join(fields)
|
||||
timestamp = '{p[%s].value}' % timestamp_index
|
||||
if precision == WritePrecision.US:
|
||||
timestamp = '{int(p[%s].value / 1e3)}' % timestamp_index
|
||||
elif precision == WritePrecision.MS:
|
||||
timestamp = '{int(p[%s].value / 1e6)}' % timestamp_index
|
||||
elif precision == WritePrecision.S:
|
||||
timestamp = '{int(p[%s].value / 1e9)}' % timestamp_index
|
||||
|
||||
f = eval(f'lambda p: f"""{{measurement_name}}{tags} {fields} {timestamp}"""', {
|
||||
'measurement_name': measurement_name,
|
||||
'_ESCAPE_KEY': _ESCAPE_KEY,
|
||||
'_ESCAPE_STRING': _ESCAPE_STRING,
|
||||
'keys': keys,
|
||||
'pd': pd,
|
||||
})
|
||||
|
||||
for k, v in dict(data_frame.dtypes).items():
|
||||
if k in data_frame_tag_columns:
|
||||
data_frame = data_frame.replace({k: ''}, np.nan)
|
||||
|
||||
def _any_not_nan(p, indexes):
|
||||
return any(map(lambda x: not pd.isna(p[x]), indexes))
|
||||
|
||||
self.data_frame = data_frame
|
||||
self.f = f
|
||||
self.field_indexes = field_indexes
|
||||
self.first_field_maybe_null = null_columns.iloc[field_indexes[0] - 1]
|
||||
self._any_not_nan = _any_not_nan
|
||||
|
||||
#
|
||||
# prepare chunks
|
||||
#
|
||||
if chunk_size is not None:
|
||||
self.number_of_chunks = int(math.ceil(len(data_frame) / float(chunk_size)))
|
||||
self.chunk_size = chunk_size
|
||||
else:
|
||||
self.number_of_chunks = None
|
||||
|
||||
def serialize(self, chunk_idx: int = None):
|
||||
"""
|
||||
Serialize chunk into LineProtocols.
|
||||
|
||||
:param chunk_idx: The index of chunk to serialize. If `None` then serialize whole dataframe.
|
||||
"""
|
||||
if chunk_idx is None:
|
||||
chunk = self.data_frame
|
||||
else:
|
||||
logger.debug("Serialize chunk %s/%s ...", chunk_idx + 1, self.number_of_chunks)
|
||||
chunk = self.data_frame[chunk_idx * self.chunk_size:(chunk_idx + 1) * self.chunk_size]
|
||||
|
||||
if self.first_field_maybe_null:
|
||||
# When the first field is null (None/NaN), we'll have
|
||||
# a spurious leading comma which needs to be removed.
|
||||
lp = (re.sub('^(( |[^ ])* ),([a-zA-Z0-9])(.*)', '\\1\\3\\4', self.f(p))
|
||||
for p in filter(lambda x: self._any_not_nan(x, self.field_indexes), _itertuples(chunk)))
|
||||
return list(lp)
|
||||
else:
|
||||
return list(map(self.f, _itertuples(chunk)))
|
||||
|
||||
def number_of_chunks(self):
|
||||
"""
|
||||
Return the number of chunks.
|
||||
|
||||
:return: number of chunks or None if chunk_size is not specified.
|
||||
"""
|
||||
return self.number_of_chunks
|
||||
|
||||
|
||||
def data_frame_to_list_of_points(data_frame, point_settings, precision=DEFAULT_WRITE_PRECISION, **kwargs):
|
||||
"""
|
||||
Serialize DataFrame into LineProtocols.
|
||||
|
||||
:param data_frame: Pandas DataFrame to serialize
|
||||
:param point_settings: Default Tags
|
||||
:param precision: The precision for the unix timestamps within the body line-protocol.
|
||||
:key data_frame_measurement_name: name of measurement for writing Pandas DataFrame
|
||||
:key data_frame_tag_columns: list of DataFrame columns which are tags, rest columns will be fields
|
||||
:key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
|
||||
formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
|
||||
or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
|
||||
:key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
|
||||
""" # noqa: E501
|
||||
return DataframeSerializer(data_frame, point_settings, precision, **kwargs).serialize()
|
||||
@@ -1,371 +0,0 @@
|
||||
"""Point data structure to represent LineProtocol."""
|
||||
|
||||
import math
|
||||
import warnings
|
||||
from builtins import int
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from numbers import Integral
|
||||
|
||||
from influxdb_client.client.util.date_utils import get_date_helper
|
||||
from influxdb_client.domain.write_precision import WritePrecision
|
||||
|
||||
EPOCH = datetime.fromtimestamp(0, tz=timezone.utc)
|
||||
|
||||
DEFAULT_WRITE_PRECISION = WritePrecision.NS
|
||||
|
||||
_ESCAPE_MEASUREMENT = str.maketrans({
|
||||
',': r'\,',
|
||||
' ': r'\ ',
|
||||
'\n': r'\n',
|
||||
'\t': r'\t',
|
||||
'\r': r'\r',
|
||||
})
|
||||
|
||||
_ESCAPE_KEY = str.maketrans({
|
||||
',': r'\,',
|
||||
'=': r'\=',
|
||||
' ': r'\ ',
|
||||
'\n': r'\n',
|
||||
'\t': r'\t',
|
||||
'\r': r'\r',
|
||||
})
|
||||
|
||||
_ESCAPE_STRING = str.maketrans({
|
||||
'"': r'\"',
|
||||
'\\': r'\\',
|
||||
})
|
||||
|
||||
try:
|
||||
import numpy as np
|
||||
|
||||
_HAS_NUMPY = True
|
||||
except ModuleNotFoundError:
|
||||
_HAS_NUMPY = False
|
||||
|
||||
|
||||
class Point(object):
|
||||
"""
|
||||
Point defines the values that will be written to the database.
|
||||
|
||||
Ref: https://docs.influxdata.com/influxdb/latest/reference/key-concepts/data-elements/#point
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def measurement(measurement):
|
||||
"""Create a new Point with specified measurement name."""
|
||||
p = Point(measurement)
|
||||
return p
|
||||
|
||||
@staticmethod
|
||||
def from_dict(dictionary: dict, write_precision: WritePrecision = DEFAULT_WRITE_PRECISION, **kwargs):
|
||||
"""
|
||||
Initialize point from 'dict' structure.
|
||||
|
||||
The expected dict structure is:
|
||||
- measurement
|
||||
- tags
|
||||
- fields
|
||||
- time
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# Use default dictionary structure
|
||||
dict_structure = {
|
||||
"measurement": "h2o_feet",
|
||||
"tags": {"location": "coyote_creek"},
|
||||
"fields": {"water_level": 1.0},
|
||||
"time": 1
|
||||
}
|
||||
point = Point.from_dict(dict_structure, WritePrecision.NS)
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# Use custom dictionary structure
|
||||
dictionary = {
|
||||
"name": "sensor_pt859",
|
||||
"location": "warehouse_125",
|
||||
"version": "2021.06.05.5874",
|
||||
"pressure": 125,
|
||||
"temperature": 10,
|
||||
"created": 1632208639,
|
||||
}
|
||||
point = Point.from_dict(dictionary,
|
||||
write_precision=WritePrecision.S,
|
||||
record_measurement_key="name",
|
||||
record_time_key="created",
|
||||
record_tag_keys=["location", "version"],
|
||||
record_field_keys=["pressure", "temperature"])
|
||||
|
||||
Int Types:
|
||||
The following example shows how to configure the types of integers fields.
|
||||
It is useful when you want to serialize integers always as ``float`` to avoid ``field type conflict``
|
||||
or use ``unsigned 64-bit integer`` as the type for serialization.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Use custom dictionary structure
|
||||
dict_structure = {
|
||||
"measurement": "h2o_feet",
|
||||
"tags": {"location": "coyote_creek"},
|
||||
"fields": {
|
||||
"water_level": 1.0,
|
||||
"some_counter": 108913123234
|
||||
},
|
||||
"time": 1
|
||||
}
|
||||
|
||||
point = Point.from_dict(dict_structure, field_types={"some_counter": "uint"})
|
||||
|
||||
:param dictionary: dictionary for serialize into data Point
|
||||
:param write_precision: sets the precision for the supplied time values
|
||||
:key record_measurement_key: key of dictionary with specified measurement
|
||||
:key record_measurement_name: static measurement name for data Point
|
||||
:key record_time_key: key of dictionary with specified timestamp
|
||||
:key record_tag_keys: list of dictionary keys to use as a tag
|
||||
:key record_field_keys: list of dictionary keys to use as a field
|
||||
:key field_types: optional dictionary to specify types of serialized fields. Currently, is supported customization for integer types.
|
||||
Possible integers types:
|
||||
- ``int`` - serialize integers as "**Signed 64-bit integers**" - ``9223372036854775807i`` (default behaviour)
|
||||
- ``uint`` - serialize integers as "**Unsigned 64-bit integers**" - ``9223372036854775807u``
|
||||
- ``float`` - serialize integers as "**IEEE-754 64-bit floating-point numbers**". Useful for unify number types in your pipeline to avoid field type conflict - ``9223372036854775807``
|
||||
The ``field_types`` can be also specified as part of incoming dictionary. For more info see an example above.
|
||||
:return: new data point
|
||||
""" # noqa: E501
|
||||
measurement_ = kwargs.get('record_measurement_name', None)
|
||||
if measurement_ is None:
|
||||
measurement_ = dictionary[kwargs.get('record_measurement_key', 'measurement')]
|
||||
point = Point(measurement_)
|
||||
|
||||
record_tag_keys = kwargs.get('record_tag_keys', None)
|
||||
if record_tag_keys is not None:
|
||||
for tag_key in record_tag_keys:
|
||||
if tag_key in dictionary:
|
||||
point.tag(tag_key, dictionary[tag_key])
|
||||
elif 'tags' in dictionary:
|
||||
for tag_key, tag_value in dictionary['tags'].items():
|
||||
point.tag(tag_key, tag_value)
|
||||
|
||||
record_field_keys = kwargs.get('record_field_keys', None)
|
||||
if record_field_keys is not None:
|
||||
for field_key in record_field_keys:
|
||||
if field_key in dictionary:
|
||||
point.field(field_key, dictionary[field_key])
|
||||
else:
|
||||
for field_key, field_value in dictionary['fields'].items():
|
||||
point.field(field_key, field_value)
|
||||
|
||||
record_time_key = kwargs.get('record_time_key', 'time')
|
||||
if record_time_key in dictionary:
|
||||
point.time(dictionary[record_time_key], write_precision=write_precision)
|
||||
|
||||
_field_types = kwargs.get('field_types', {})
|
||||
if 'field_types' in dictionary:
|
||||
_field_types = dictionary['field_types']
|
||||
# Map API fields types to Line Protocol types postfix:
|
||||
# - int: 'i'
|
||||
# - uint: 'u'
|
||||
# - float: ''
|
||||
point._field_types = dict(map(
|
||||
lambda item: (item[0], 'i' if item[1] == 'int' else 'u' if item[1] == 'uint' else ''),
|
||||
_field_types.items()
|
||||
))
|
||||
|
||||
return point
|
||||
|
||||
def __init__(self, measurement_name):
|
||||
"""Initialize defaults."""
|
||||
self._tags = {}
|
||||
self._fields = {}
|
||||
self._name = measurement_name
|
||||
self._time = None
|
||||
self._write_precision = DEFAULT_WRITE_PRECISION
|
||||
self._field_types = {}
|
||||
|
||||
def time(self, time, write_precision=DEFAULT_WRITE_PRECISION):
|
||||
"""
|
||||
Specify timestamp for DataPoint with declared precision.
|
||||
|
||||
If time doesn't have specified timezone we assume that timezone is UTC.
|
||||
|
||||
Examples::
|
||||
Point.measurement("h2o").field("val", 1).time("2009-11-10T23:00:00.123456Z")
|
||||
Point.measurement("h2o").field("val", 1).time(1257894000123456000)
|
||||
Point.measurement("h2o").field("val", 1).time(datetime(2009, 11, 10, 23, 0, 0, 123456))
|
||||
Point.measurement("h2o").field("val", 1).time(1257894000123456000, write_precision=WritePrecision.NS)
|
||||
|
||||
|
||||
:param time: the timestamp for your data
|
||||
:param write_precision: sets the precision for the supplied time values
|
||||
:return: this point
|
||||
"""
|
||||
self._write_precision = write_precision
|
||||
self._time = time
|
||||
return self
|
||||
|
||||
def tag(self, key, value):
|
||||
"""Add tag with key and value."""
|
||||
self._tags[key] = value
|
||||
return self
|
||||
|
||||
def field(self, field, value):
|
||||
"""Add field with key and value."""
|
||||
self._fields[field] = value
|
||||
return self
|
||||
|
||||
def to_line_protocol(self, precision=None):
|
||||
"""
|
||||
Create LineProtocol.
|
||||
|
||||
:param precision: required precision of LineProtocol. If it's not set then use the precision from ``Point``.
|
||||
"""
|
||||
_measurement = _escape_key(self._name, _ESCAPE_MEASUREMENT)
|
||||
if _measurement.startswith("#"):
|
||||
message = f"""The measurement name '{_measurement}' start with '#'.
|
||||
|
||||
The output Line protocol will be interpret as a comment by InfluxDB. For more info see:
|
||||
- https://docs.influxdata.com/influxdb/latest/reference/syntax/line-protocol/#comments
|
||||
"""
|
||||
warnings.warn(message, SyntaxWarning)
|
||||
_tags = _append_tags(self._tags)
|
||||
_fields = _append_fields(self._fields, self._field_types)
|
||||
if not _fields:
|
||||
return ""
|
||||
_time = _append_time(self._time, self._write_precision if precision is None else precision)
|
||||
|
||||
return f"{_measurement}{_tags}{_fields}{_time}"
|
||||
|
||||
@property
|
||||
def write_precision(self):
|
||||
"""Get precision."""
|
||||
return self._write_precision
|
||||
|
||||
@classmethod
|
||||
def set_str_rep(cls, rep_function):
|
||||
"""Set the string representation for all Points."""
|
||||
cls.__str___rep = rep_function
|
||||
|
||||
def __str__(self):
|
||||
"""Create string representation of this Point."""
|
||||
return self.to_line_protocol()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Return true iff other is equal to self."""
|
||||
if not isinstance(other, Point):
|
||||
return False
|
||||
# assume points are equal iff their instance fields are equal
|
||||
return (self._tags == other._tags and
|
||||
self._fields == other._fields and
|
||||
self._name == other._name and
|
||||
self._time == other._time and
|
||||
self._write_precision == other._write_precision and
|
||||
self._field_types == other._field_types)
|
||||
|
||||
|
||||
def _append_tags(tags):
|
||||
_return = []
|
||||
for tag_key, tag_value in sorted(tags.items()):
|
||||
|
||||
if tag_value is None:
|
||||
continue
|
||||
|
||||
tag = _escape_key(tag_key)
|
||||
value = _escape_tag_value(tag_value)
|
||||
if tag != '' and value != '':
|
||||
_return.append(f'{tag}={value}')
|
||||
|
||||
return f"{',' if _return else ''}{','.join(_return)} "
|
||||
|
||||
|
||||
def _append_fields(fields, field_types):
|
||||
_return = []
|
||||
|
||||
for field, value in sorted(fields.items()):
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, float) or isinstance(value, Decimal) or _np_is_subtype(value, 'float'):
|
||||
if not math.isfinite(value):
|
||||
continue
|
||||
s = str(value)
|
||||
# It's common to represent whole numbers as floats
|
||||
# and the trailing ".0" that Python produces is unnecessary
|
||||
# in line-protocol, inconsistent with other line-protocol encoders,
|
||||
# and takes more space than needed, so trim it off.
|
||||
if s.endswith('.0'):
|
||||
s = s[:-2]
|
||||
_return.append(f'{_escape_key(field)}={s}')
|
||||
elif (isinstance(value, int) or _np_is_subtype(value, 'int')) and not isinstance(value, bool):
|
||||
_type = field_types.get(field, "i")
|
||||
_return.append(f'{_escape_key(field)}={str(value)}{_type}')
|
||||
elif isinstance(value, bool):
|
||||
_return.append(f'{_escape_key(field)}={str(value).lower()}')
|
||||
elif isinstance(value, str):
|
||||
_return.append(f'{_escape_key(field)}="{_escape_string(value)}"')
|
||||
else:
|
||||
raise ValueError(f'Type: "{type(value)}" of field: "{field}" is not supported.')
|
||||
|
||||
return f"{','.join(_return)}"
|
||||
|
||||
|
||||
def _append_time(time, write_precision) -> str:
|
||||
if time is None:
|
||||
return ''
|
||||
return f" {int(_convert_timestamp(time, write_precision))}"
|
||||
|
||||
|
||||
def _escape_key(tag, escape_list=None) -> str:
|
||||
if escape_list is None:
|
||||
escape_list = _ESCAPE_KEY
|
||||
return str(tag).translate(escape_list)
|
||||
|
||||
|
||||
def _escape_tag_value(value) -> str:
|
||||
ret = _escape_key(value)
|
||||
if ret.endswith('\\'):
|
||||
ret += ' '
|
||||
return ret
|
||||
|
||||
|
||||
def _escape_string(value) -> str:
|
||||
return str(value).translate(_ESCAPE_STRING)
|
||||
|
||||
|
||||
def _convert_timestamp(timestamp, precision=DEFAULT_WRITE_PRECISION):
|
||||
date_helper = get_date_helper()
|
||||
if isinstance(timestamp, Integral):
|
||||
return timestamp # assume precision is correct if timestamp is int
|
||||
|
||||
if isinstance(timestamp, str):
|
||||
timestamp = date_helper.parse_date(timestamp)
|
||||
|
||||
if isinstance(timestamp, timedelta) or isinstance(timestamp, datetime):
|
||||
|
||||
if isinstance(timestamp, datetime):
|
||||
timestamp = date_helper.to_utc(timestamp) - EPOCH
|
||||
|
||||
ns = date_helper.to_nanoseconds(timestamp)
|
||||
|
||||
if precision is None or precision == WritePrecision.NS:
|
||||
return ns
|
||||
elif precision == WritePrecision.US:
|
||||
return ns / 1e3
|
||||
elif precision == WritePrecision.MS:
|
||||
return ns / 1e6
|
||||
elif precision == WritePrecision.S:
|
||||
return ns / 1e9
|
||||
|
||||
raise ValueError(timestamp)
|
||||
|
||||
|
||||
def _np_is_subtype(value, np_type):
|
||||
if not _HAS_NUMPY or not hasattr(value, 'dtype'):
|
||||
return False
|
||||
|
||||
if np_type == 'float':
|
||||
return np.issubdtype(value, np.floating)
|
||||
elif np_type == 'int':
|
||||
return np.issubdtype(value, np.integer)
|
||||
return False
|
||||
@@ -1,148 +0,0 @@
|
||||
"""Implementation for Retry strategy during HTTP requests."""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import takewhile
|
||||
from random import random
|
||||
from typing import Callable
|
||||
|
||||
from urllib3 import Retry
|
||||
from urllib3.exceptions import MaxRetryError, ResponseError
|
||||
|
||||
from influxdb_client.client.exceptions import InfluxDBError
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.write.retry')
|
||||
|
||||
|
||||
class WritesRetry(Retry):
|
||||
"""
|
||||
Writes retry configuration.
|
||||
|
||||
The next delay is computed as random value between range
|
||||
`retry_interval * exponential_base^(attempts-1)` and `retry_interval * exponential_base^(attempts)
|
||||
|
||||
Example:
|
||||
for retry_interval=5, exponential_base=2, max_retry_delay=125, total=5
|
||||
retry delays are random distributed values within the ranges of
|
||||
[5-10, 10-20, 20-40, 40-80, 80-125]
|
||||
"""
|
||||
|
||||
def __init__(self, jitter_interval=0, max_retry_delay=125, exponential_base=2, max_retry_time=180, total=5,
|
||||
retry_interval=5, retry_callback: Callable[[Exception], int] = None, **kw):
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param int jitter_interval: random milliseconds when retrying writes
|
||||
:param num max_retry_delay: maximum delay when retrying write in seconds
|
||||
:param int max_retry_time: maximum total retry timeout in seconds,
|
||||
attempt after this timout throws MaxRetryError
|
||||
:param int total: maximum number of retries
|
||||
:param num retry_interval: initial first retry delay range in seconds
|
||||
:param int exponential_base: base for the exponential retry delay,
|
||||
:param Callable[[Exception], int] retry_callback: the callable ``callback`` to run after retryable
|
||||
error occurred.
|
||||
The callable must accept one argument:
|
||||
- `Exception`: an retryable error
|
||||
"""
|
||||
super().__init__(**kw)
|
||||
self.jitter_interval = jitter_interval
|
||||
self.total = total
|
||||
self.retry_interval = retry_interval
|
||||
self.max_retry_delay = max_retry_delay
|
||||
self.max_retry_time = max_retry_time
|
||||
self.exponential_base = exponential_base
|
||||
self.retry_timeout = datetime.now() + timedelta(seconds=max_retry_time)
|
||||
self.retry_callback = retry_callback
|
||||
|
||||
def new(self, **kw):
|
||||
"""Initialize defaults."""
|
||||
if 'jitter_interval' not in kw:
|
||||
kw['jitter_interval'] = self.jitter_interval
|
||||
if 'retry_interval' not in kw:
|
||||
kw['retry_interval'] = self.retry_interval
|
||||
if 'max_retry_delay' not in kw:
|
||||
kw['max_retry_delay'] = self.max_retry_delay
|
||||
if 'max_retry_time' not in kw:
|
||||
kw['max_retry_time'] = self.max_retry_time
|
||||
if 'exponential_base' not in kw:
|
||||
kw['exponential_base'] = self.exponential_base
|
||||
if 'retry_callback' not in kw:
|
||||
kw['retry_callback'] = self.retry_callback
|
||||
|
||||
new = super().new(**kw)
|
||||
new.retry_timeout = self.retry_timeout
|
||||
return new
|
||||
|
||||
def is_retry(self, method, status_code, has_retry_after=False):
|
||||
"""is_retry doesn't require retry_after header. If there is not Retry-After we will use backoff."""
|
||||
if not self._is_method_retryable(method):
|
||||
return False
|
||||
|
||||
return self.total and (status_code >= 429)
|
||||
|
||||
def get_backoff_time(self):
|
||||
"""Variant of exponential backoff with initial and max delay and a random jitter delay."""
|
||||
# We want to consider only the last consecutive errors sequence (Ignore redirects).
|
||||
consecutive_errors_len = len(
|
||||
list(
|
||||
takewhile(lambda x: x.redirect_location is None, reversed(self.history))
|
||||
)
|
||||
)
|
||||
# First fail doesn't increase backoff
|
||||
consecutive_errors_len -= 1
|
||||
if consecutive_errors_len < 0:
|
||||
return 0
|
||||
|
||||
range_start = self.retry_interval
|
||||
range_stop = self.retry_interval * self.exponential_base
|
||||
|
||||
i = 1
|
||||
while i <= consecutive_errors_len:
|
||||
i += 1
|
||||
range_start = range_stop
|
||||
range_stop = range_stop * self.exponential_base
|
||||
if range_stop > self.max_retry_delay:
|
||||
break
|
||||
|
||||
if range_stop > self.max_retry_delay:
|
||||
range_stop = self.max_retry_delay
|
||||
|
||||
return range_start + (range_stop - range_start) * self._random()
|
||||
|
||||
def get_retry_after(self, response):
|
||||
"""Get the value of Retry-After header and append random jitter delay."""
|
||||
retry_after = super().get_retry_after(response)
|
||||
if retry_after:
|
||||
retry_after += self._jitter_delay()
|
||||
return retry_after
|
||||
|
||||
def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
|
||||
"""Return a new Retry object with incremented retry counters."""
|
||||
if self.retry_timeout < datetime.now():
|
||||
raise MaxRetryError(_pool, url, error or ResponseError("max_retry_time exceeded"))
|
||||
|
||||
new_retry = super().increment(method, url, response, error, _pool, _stacktrace)
|
||||
|
||||
if response is not None:
|
||||
parsed_error = InfluxDBError(response=response)
|
||||
elif error is not None:
|
||||
parsed_error = error
|
||||
else:
|
||||
parsed_error = f"Failed request to: {url}"
|
||||
|
||||
message = f"The retriable error occurred during request. Reason: '{parsed_error}'."
|
||||
if isinstance(parsed_error, InfluxDBError):
|
||||
message += f" Retry in {parsed_error.retry_after}s."
|
||||
|
||||
if self.retry_callback:
|
||||
self.retry_callback(parsed_error)
|
||||
|
||||
logger.warning(message)
|
||||
|
||||
return new_retry
|
||||
|
||||
def _jitter_delay(self):
|
||||
return self.jitter_interval * random()
|
||||
|
||||
def _random(self):
|
||||
return random()
|
||||
@@ -1,587 +0,0 @@
|
||||
"""Collect and write time series data to InfluxDB Cloud or InfluxDB OSS."""
|
||||
|
||||
# coding: utf-8
|
||||
import logging
|
||||
import os
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from datetime import timedelta
|
||||
from enum import Enum
|
||||
from random import random
|
||||
from time import sleep
|
||||
from typing import Union, Any, Iterable, NamedTuple
|
||||
|
||||
import reactivex as rx
|
||||
from reactivex import operators as ops, Observable
|
||||
from reactivex.scheduler import ThreadPoolScheduler
|
||||
from reactivex.subject import Subject
|
||||
|
||||
from influxdb_client import WritePrecision
|
||||
from influxdb_client.client._base import _BaseWriteApi, _HAS_DATACLASS
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
from influxdb_client.client.write.dataframe_serializer import DataframeSerializer
|
||||
from influxdb_client.client.write.point import Point, DEFAULT_WRITE_PRECISION
|
||||
from influxdb_client.client.write.retry import WritesRetry
|
||||
from influxdb_client.rest import _UTF_8_encoding
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.write_api')
|
||||
|
||||
|
||||
if _HAS_DATACLASS:
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class WriteType(Enum):
|
||||
"""Configuration which type of writes will client use."""
|
||||
|
||||
batching = 1
|
||||
asynchronous = 2
|
||||
synchronous = 3
|
||||
|
||||
|
||||
class WriteOptions(object):
|
||||
"""Write configuration."""
|
||||
|
||||
def __init__(self, write_type: WriteType = WriteType.batching,
|
||||
batch_size=1_000, flush_interval=1_000,
|
||||
jitter_interval=0,
|
||||
retry_interval=5_000,
|
||||
max_retries=5,
|
||||
max_retry_delay=125_000,
|
||||
max_retry_time=180_000,
|
||||
exponential_base=2,
|
||||
max_close_wait=300_000,
|
||||
write_scheduler=ThreadPoolScheduler(max_workers=1)) -> None:
|
||||
"""
|
||||
Create write api configuration.
|
||||
|
||||
:param write_type: methods of write (batching, asynchronous, synchronous)
|
||||
:param batch_size: the number of data point to collect in batch
|
||||
:param flush_interval: flush data at least in this interval (milliseconds)
|
||||
:param jitter_interval: this is primarily to avoid large write spikes for users running a large number of
|
||||
client instances ie, a jitter of 5s and flush duration 10s means flushes will happen every 10-15s
|
||||
(milliseconds)
|
||||
:param retry_interval: the time to wait before retry unsuccessful write (milliseconds)
|
||||
:param max_retries: the number of max retries when write fails, 0 means retry is disabled
|
||||
:param max_retry_delay: the maximum delay between each retry attempt in milliseconds
|
||||
:param max_retry_time: total timeout for all retry attempts in milliseconds, if 0 retry is disabled
|
||||
:param exponential_base: base for the exponential retry delay
|
||||
:parama max_close_wait: the maximum time to wait for writes to be flushed if close() is called
|
||||
:param write_scheduler:
|
||||
"""
|
||||
self.write_type = write_type
|
||||
self.batch_size = batch_size
|
||||
self.flush_interval = flush_interval
|
||||
self.jitter_interval = jitter_interval
|
||||
self.retry_interval = retry_interval
|
||||
self.max_retries = max_retries
|
||||
self.max_retry_delay = max_retry_delay
|
||||
self.max_retry_time = max_retry_time
|
||||
self.exponential_base = exponential_base
|
||||
self.write_scheduler = write_scheduler
|
||||
self.max_close_wait = max_close_wait
|
||||
|
||||
def to_retry_strategy(self, **kwargs):
|
||||
"""
|
||||
Create a Retry strategy from write options.
|
||||
|
||||
:key retry_callback: The callable ``callback`` to run after retryable error occurred.
|
||||
The callable must accept one argument:
|
||||
- `Exception`: an retryable error
|
||||
"""
|
||||
return WritesRetry(
|
||||
total=self.max_retries,
|
||||
retry_interval=self.retry_interval / 1_000,
|
||||
jitter_interval=self.jitter_interval / 1_000,
|
||||
max_retry_delay=self.max_retry_delay / 1_000,
|
||||
max_retry_time=self.max_retry_time / 1_000,
|
||||
exponential_base=self.exponential_base,
|
||||
retry_callback=kwargs.get("retry_callback", None),
|
||||
allowed_methods=["POST"])
|
||||
|
||||
def __getstate__(self):
|
||||
"""Return a dict of attributes that you want to pickle."""
|
||||
state = self.__dict__.copy()
|
||||
# Remove write scheduler
|
||||
del state['write_scheduler']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Set your object with the provided dict."""
|
||||
self.__dict__.update(state)
|
||||
# Init default write Scheduler
|
||||
self.write_scheduler = ThreadPoolScheduler(max_workers=1)
|
||||
|
||||
|
||||
SYNCHRONOUS = WriteOptions(write_type=WriteType.synchronous)
|
||||
ASYNCHRONOUS = WriteOptions(write_type=WriteType.asynchronous)
|
||||
|
||||
|
||||
class PointSettings(object):
|
||||
"""Settings to store default tags."""
|
||||
|
||||
def __init__(self, **default_tags) -> None:
|
||||
"""
|
||||
Create point settings for write api.
|
||||
|
||||
:param default_tags: Default tags which will be added to each point written by api.
|
||||
"""
|
||||
self.defaultTags = dict()
|
||||
|
||||
for key, val in default_tags.items():
|
||||
self.add_default_tag(key, val)
|
||||
|
||||
@staticmethod
|
||||
def _get_value(value):
|
||||
|
||||
if value.startswith("${env."):
|
||||
return os.environ.get(value[6:-1])
|
||||
|
||||
return value
|
||||
|
||||
def add_default_tag(self, key, value) -> None:
|
||||
"""Add new default tag with key and value."""
|
||||
self.defaultTags[key] = self._get_value(value)
|
||||
|
||||
|
||||
class _BatchItemKey(object):
|
||||
def __init__(self, bucket, org, precision=DEFAULT_WRITE_PRECISION) -> None:
|
||||
self.bucket = bucket
|
||||
self.org = org
|
||||
self.precision = precision
|
||||
pass
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((self.bucket, self.org, self.precision))
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
return isinstance(o, self.__class__) \
|
||||
and self.bucket == o.bucket and self.org == o.org and self.precision == o.precision
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '_BatchItemKey[bucket:\'{}\', org:\'{}\', precision:\'{}\']' \
|
||||
.format(str(self.bucket), str(self.org), str(self.precision))
|
||||
|
||||
|
||||
class _BatchItem(object):
|
||||
def __init__(self, key: _BatchItemKey, data, size=1) -> None:
|
||||
self.key = key
|
||||
self.data = data
|
||||
self.size = size
|
||||
pass
|
||||
|
||||
def to_key_tuple(self) -> (str, str, str):
|
||||
return self.key.bucket, self.key.org, self.key.precision
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '_BatchItem[key:\'{}\', size: \'{}\']' \
|
||||
.format(str(self.key), str(self.size))
|
||||
|
||||
|
||||
class _BatchResponse(object):
|
||||
def __init__(self, data: _BatchItem, exception: Exception = None):
|
||||
self.data = data
|
||||
self.exception = exception
|
||||
pass
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '_BatchResponse[status:\'{}\', \'{}\']' \
|
||||
.format("failed" if self.exception else "success", str(self.data))
|
||||
|
||||
|
||||
def _body_reduce(batch_items):
|
||||
return b'\n'.join(map(lambda batch_item: batch_item.data, batch_items))
|
||||
|
||||
|
||||
class WriteApi(_BaseWriteApi):
|
||||
"""
|
||||
Implementation for '/api/v2/write' endpoint.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client import InfluxDBClient
|
||||
from influxdb_client.client.write_api import SYNCHRONOUS
|
||||
|
||||
|
||||
# Initialize SYNCHRONOUS instance of WriteApi
|
||||
with InfluxDBClient(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
write_api = client.write_api(write_options=SYNCHRONOUS)
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
influxdb_client,
|
||||
write_options: WriteOptions = WriteOptions(),
|
||||
point_settings: PointSettings = PointSettings(),
|
||||
**kwargs) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param influxdb_client: with default settings (organization)
|
||||
:param write_options: write api configuration
|
||||
:param point_settings: settings to store default tags.
|
||||
:key success_callback: The callable ``callback`` to run after successfully writen a batch.
|
||||
|
||||
The callable must accept two arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
|
||||
**[batching mode]**
|
||||
:key error_callback: The callable ``callback`` to run after unsuccessfully writen a batch.
|
||||
|
||||
The callable must accept three arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
- `Exception`: an occurred error
|
||||
|
||||
**[batching mode]**
|
||||
:key retry_callback: The callable ``callback`` to run after retryable error occurred.
|
||||
|
||||
The callable must accept three arguments:
|
||||
- `Tuple`: ``(bucket, organization, precision)``
|
||||
- `str`: written data
|
||||
- `Exception`: an retryable error
|
||||
|
||||
**[batching mode]**
|
||||
"""
|
||||
super().__init__(influxdb_client=influxdb_client, point_settings=point_settings)
|
||||
self._write_options = write_options
|
||||
self._success_callback = kwargs.get('success_callback', None)
|
||||
self._error_callback = kwargs.get('error_callback', None)
|
||||
self._retry_callback = kwargs.get('retry_callback', None)
|
||||
self._window_scheduler = None
|
||||
|
||||
if self._write_options.write_type is WriteType.batching:
|
||||
# Define Subject that listen incoming data and produces writes into InfluxDB
|
||||
self._subject = Subject()
|
||||
|
||||
self._window_scheduler = ThreadPoolScheduler(1)
|
||||
self._disposable = self._subject.pipe(
|
||||
# Split incoming data to windows by batch_size or flush_interval
|
||||
ops.window_with_time_or_count(count=write_options.batch_size,
|
||||
timespan=timedelta(milliseconds=write_options.flush_interval),
|
||||
scheduler=self._window_scheduler),
|
||||
# Map window into groups defined by 'organization', 'bucket' and 'precision'
|
||||
ops.flat_map(lambda window: window.pipe(
|
||||
# Group window by 'organization', 'bucket' and 'precision'
|
||||
ops.group_by(lambda batch_item: batch_item.key),
|
||||
# Create batch (concatenation line protocols by \n)
|
||||
ops.map(lambda group: group.pipe(
|
||||
ops.to_iterable(),
|
||||
ops.map(lambda xs: _BatchItem(key=group.key, data=_body_reduce(xs), size=len(xs))))),
|
||||
ops.merge_all())),
|
||||
# Write data into InfluxDB (possibility to retry if its fail)
|
||||
ops.filter(lambda batch: batch.size > 0),
|
||||
ops.map(mapper=lambda batch: self._to_response(data=batch, delay=self._jitter_delay())),
|
||||
ops.merge_all()) \
|
||||
.subscribe(self._on_next, self._on_error, self._on_complete)
|
||||
|
||||
else:
|
||||
self._subject = None
|
||||
self._disposable = None
|
||||
|
||||
if self._write_options.write_type is WriteType.asynchronous:
|
||||
message = """The 'WriteType.asynchronous' is deprecated and will be removed in future major version.
|
||||
|
||||
You can use native asynchronous version of the client:
|
||||
- https://influxdb-client.readthedocs.io/en/stable/usage.html#how-to-use-asyncio
|
||||
"""
|
||||
warnings.warn(message, DeprecationWarning)
|
||||
|
||||
def write(self, bucket: str, org: str = None,
|
||||
record: Union[
|
||||
str, Iterable['str'], Point, Iterable['Point'], dict, Iterable['dict'], bytes, Iterable['bytes'],
|
||||
Observable, NamedTuple, Iterable['NamedTuple'], 'dataclass', Iterable['dataclass']
|
||||
] = None,
|
||||
write_precision: WritePrecision = DEFAULT_WRITE_PRECISION, **kwargs) -> Any:
|
||||
"""
|
||||
Write time-series data into InfluxDB.
|
||||
|
||||
:param str bucket: specifies the destination bucket for writes (required)
|
||||
:param str, Organization org: specifies the destination organization for writes;
|
||||
take the ID, Name or Organization.
|
||||
If not specified the default value from ``InfluxDBClient.org`` is used.
|
||||
:param WritePrecision write_precision: specifies the precision for the unix timestamps within
|
||||
the body line-protocol. The precision specified on a Point has precedes
|
||||
and is use for write.
|
||||
:param record: Point, Line Protocol, Dictionary, NamedTuple, Data Classes, Pandas DataFrame or
|
||||
RxPY Observable to write
|
||||
:key data_frame_measurement_name: name of measurement for writing Pandas DataFrame - ``DataFrame``
|
||||
:key data_frame_tag_columns: list of DataFrame columns which are tags,
|
||||
rest columns will be fields - ``DataFrame``
|
||||
:key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
|
||||
formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
|
||||
or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
|
||||
:key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
|
||||
:key record_measurement_key: key of record with specified measurement -
|
||||
``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_measurement_name: static measurement name - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_time_key: key of record with specified timestamp - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_tag_keys: list of record keys to use as a tag - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_field_keys: list of record keys to use as a field - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# Record as Line Protocol
|
||||
write_api.write("my-bucket", "my-org", "h2o_feet,location=us-west level=125i 1")
|
||||
|
||||
# Record as Dictionary
|
||||
dictionary = {
|
||||
"measurement": "h2o_feet",
|
||||
"tags": {"location": "us-west"},
|
||||
"fields": {"level": 125},
|
||||
"time": 1
|
||||
}
|
||||
write_api.write("my-bucket", "my-org", dictionary)
|
||||
|
||||
# Record as Point
|
||||
from influxdb_client import Point
|
||||
point = Point("h2o_feet").tag("location", "us-west").field("level", 125).time(1)
|
||||
write_api.write("my-bucket", "my-org", point)
|
||||
|
||||
DataFrame:
|
||||
If the ``data_frame_timestamp_column`` is not specified the index of `Pandas DataFrame <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html>`_
|
||||
is used as a ``timestamp`` for written data. The index can be `PeriodIndex <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.PeriodIndex.html#pandas.PeriodIndex>`_
|
||||
or its must be transformable to ``datetime`` by
|
||||
`pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_.
|
||||
|
||||
If you would like to transform a column to ``PeriodIndex``, you can use something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# DataFrame
|
||||
data_frame = ...
|
||||
# Set column as Index
|
||||
data_frame.set_index('column_name', inplace=True)
|
||||
# Transform index to PeriodIndex
|
||||
data_frame.index = pd.to_datetime(data_frame.index, unit='s')
|
||||
|
||||
""" # noqa: E501
|
||||
org = get_org_query_param(org=org, client=self._influxdb_client)
|
||||
|
||||
self._append_default_tags(record)
|
||||
|
||||
if self._write_options.write_type is WriteType.batching:
|
||||
return self._write_batching(bucket, org, record,
|
||||
write_precision, **kwargs)
|
||||
|
||||
payloads = defaultdict(list)
|
||||
self._serialize(record, write_precision, payloads, **kwargs)
|
||||
|
||||
_async_req = True if self._write_options.write_type == WriteType.asynchronous else False
|
||||
|
||||
def write_payload(payload):
|
||||
final_string = b'\n'.join(payload[1])
|
||||
return self._post_write(_async_req, bucket, org, final_string, payload[0])
|
||||
|
||||
results = list(map(write_payload, payloads.items()))
|
||||
if not _async_req:
|
||||
return None
|
||||
elif len(results) == 1:
|
||||
return results[0]
|
||||
return results
|
||||
|
||||
def flush(self):
|
||||
"""Flush data."""
|
||||
# TODO
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
"""Flush data and dispose a batching buffer."""
|
||||
self.__del__()
|
||||
|
||||
def __enter__(self):
|
||||
"""
|
||||
Enter the runtime context related to this object.
|
||||
|
||||
It will bind this method’s return value to the target(s)
|
||||
specified in the `as` clause of the statement.
|
||||
|
||||
return: self instance
|
||||
"""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Exit the runtime context related to this object and close the WriteApi."""
|
||||
self.close()
|
||||
|
||||
def __del__(self):
|
||||
"""Close WriteApi."""
|
||||
if self._subject:
|
||||
self._subject.on_completed()
|
||||
self._subject.dispose()
|
||||
self._subject = None
|
||||
|
||||
"""
|
||||
We impose a maximum wait time to ensure that we do not cause a deadlock if the
|
||||
background thread has exited abnormally
|
||||
|
||||
Each iteration waits 100ms, but sleep expects the unit to be seconds so convert
|
||||
the maximum wait time to seconds.
|
||||
|
||||
We keep a counter of how long we've waited
|
||||
"""
|
||||
max_wait_time = self._write_options.max_close_wait / 1000
|
||||
waited = 0
|
||||
sleep_period = 0.1
|
||||
|
||||
# Wait for writing to finish
|
||||
while not self._disposable.is_disposed:
|
||||
sleep(sleep_period)
|
||||
waited += sleep_period
|
||||
|
||||
# Have we reached the upper limit?
|
||||
if waited >= max_wait_time:
|
||||
logger.warning(
|
||||
"Reached max_close_wait (%s seconds) waiting for batches to finish writing. Force closing",
|
||||
max_wait_time
|
||||
)
|
||||
break
|
||||
|
||||
if self._window_scheduler:
|
||||
self._window_scheduler.executor.shutdown(wait=False)
|
||||
self._window_scheduler = None
|
||||
|
||||
if self._disposable:
|
||||
self._disposable = None
|
||||
pass
|
||||
|
||||
def _write_batching(self, bucket, org, data,
|
||||
precision=DEFAULT_WRITE_PRECISION,
|
||||
**kwargs):
|
||||
if isinstance(data, bytes):
|
||||
_key = _BatchItemKey(bucket, org, precision)
|
||||
self._subject.on_next(_BatchItem(key=_key, data=data))
|
||||
|
||||
elif isinstance(data, str):
|
||||
self._write_batching(bucket, org, data.encode(_UTF_8_encoding),
|
||||
precision, **kwargs)
|
||||
|
||||
elif isinstance(data, Point):
|
||||
self._write_batching(bucket, org, data.to_line_protocol(), data.write_precision, **kwargs)
|
||||
|
||||
elif isinstance(data, dict):
|
||||
self._write_batching(bucket, org, Point.from_dict(data, write_precision=precision, **kwargs),
|
||||
precision, **kwargs)
|
||||
|
||||
elif 'DataFrame' in type(data).__name__:
|
||||
serializer = DataframeSerializer(data, self._point_settings, precision, self._write_options.batch_size,
|
||||
**kwargs)
|
||||
for chunk_idx in range(serializer.number_of_chunks):
|
||||
self._write_batching(bucket, org,
|
||||
serializer.serialize(chunk_idx),
|
||||
precision, **kwargs)
|
||||
elif hasattr(data, "_asdict"):
|
||||
# noinspection PyProtectedMember
|
||||
self._write_batching(bucket, org, data._asdict(), precision, **kwargs)
|
||||
|
||||
elif _HAS_DATACLASS and dataclasses.is_dataclass(data):
|
||||
self._write_batching(bucket, org, dataclasses.asdict(data), precision, **kwargs)
|
||||
|
||||
elif isinstance(data, Iterable):
|
||||
for item in data:
|
||||
self._write_batching(bucket, org, item, precision, **kwargs)
|
||||
|
||||
elif isinstance(data, Observable):
|
||||
data.subscribe(lambda it: self._write_batching(bucket, org, it, precision, **kwargs))
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def _http(self, batch_item: _BatchItem):
|
||||
|
||||
logger.debug("Write time series data into InfluxDB: %s", batch_item)
|
||||
|
||||
if self._retry_callback:
|
||||
def _retry_callback_delegate(exception):
|
||||
return self._retry_callback(batch_item.to_key_tuple(), batch_item.data, exception)
|
||||
else:
|
||||
_retry_callback_delegate = None
|
||||
|
||||
retry = self._write_options.to_retry_strategy(retry_callback=_retry_callback_delegate)
|
||||
|
||||
self._post_write(False, batch_item.key.bucket, batch_item.key.org, batch_item.data,
|
||||
batch_item.key.precision, urlopen_kw={'retries': retry})
|
||||
|
||||
logger.debug("Write request finished %s", batch_item)
|
||||
|
||||
return _BatchResponse(data=batch_item)
|
||||
|
||||
def _post_write(self, _async_req, bucket, org, body, precision, **kwargs):
|
||||
|
||||
return self._write_service.post_write(org=org, bucket=bucket, body=body, precision=precision,
|
||||
async_req=_async_req,
|
||||
content_type="text/plain; charset=utf-8",
|
||||
**kwargs)
|
||||
|
||||
def _to_response(self, data: _BatchItem, delay: timedelta):
|
||||
|
||||
return rx.of(data).pipe(
|
||||
ops.subscribe_on(self._write_options.write_scheduler),
|
||||
# use delay if its specified
|
||||
ops.delay(duetime=delay, scheduler=self._write_options.write_scheduler),
|
||||
# invoke http call
|
||||
ops.map(lambda x: self._http(x)),
|
||||
# catch exception to fail batch response
|
||||
ops.catch(handler=lambda exception, source: rx.just(_BatchResponse(exception=exception, data=data))),
|
||||
)
|
||||
|
||||
def _jitter_delay(self):
|
||||
return timedelta(milliseconds=random() * self._write_options.jitter_interval)
|
||||
|
||||
def _on_next(self, response: _BatchResponse):
|
||||
if response.exception:
|
||||
logger.error("The batch item wasn't processed successfully because: %s", response.exception)
|
||||
if self._error_callback:
|
||||
try:
|
||||
self._error_callback(response.data.to_key_tuple(), response.data.data, response.exception)
|
||||
except Exception as e:
|
||||
"""
|
||||
Unfortunately, because callbacks are user-provided generic code, exceptions can be entirely
|
||||
arbitrary
|
||||
|
||||
We trap it, log that it occurred and then proceed - there's not much more that we can
|
||||
really do.
|
||||
"""
|
||||
logger.error("The configured error callback threw an exception: %s", e)
|
||||
|
||||
else:
|
||||
logger.debug("The batch item: %s was processed successfully.", response)
|
||||
if self._success_callback:
|
||||
try:
|
||||
self._success_callback(response.data.to_key_tuple(), response.data.data)
|
||||
except Exception as e:
|
||||
logger.error("The configured success callback threw an exception: %s", e)
|
||||
|
||||
@staticmethod
|
||||
def _on_error(ex):
|
||||
logger.error("unexpected error during batching: %s", ex)
|
||||
|
||||
def _on_complete(self):
|
||||
self._disposable.dispose()
|
||||
logger.info("the batching processor was disposed")
|
||||
|
||||
def __getstate__(self):
|
||||
"""Return a dict of attributes that you want to pickle."""
|
||||
state = self.__dict__.copy()
|
||||
# Remove rx
|
||||
del state['_subject']
|
||||
del state['_disposable']
|
||||
del state['_window_scheduler']
|
||||
del state['_write_service']
|
||||
return state
|
||||
|
||||
def __setstate__(self, state):
|
||||
"""Set your object with the provided dict."""
|
||||
self.__dict__.update(state)
|
||||
# Init Rx
|
||||
self.__init__(self._influxdb_client,
|
||||
self._write_options,
|
||||
self._point_settings,
|
||||
success_callback=self._success_callback,
|
||||
error_callback=self._error_callback,
|
||||
retry_callback=self._retry_callback)
|
||||
@@ -1,134 +0,0 @@
|
||||
"""Collect and async write time series data to InfluxDB Cloud or InfluxDB OSS."""
|
||||
import logging
|
||||
from asyncio import ensure_future, gather
|
||||
from collections import defaultdict
|
||||
from typing import Union, Iterable, NamedTuple
|
||||
|
||||
from influxdb_client import Point, WritePrecision
|
||||
from influxdb_client.client._base import _BaseWriteApi, _HAS_DATACLASS
|
||||
from influxdb_client.client.util.helpers import get_org_query_param
|
||||
from influxdb_client.client.write.point import DEFAULT_WRITE_PRECISION
|
||||
from influxdb_client.client.write_api import PointSettings
|
||||
|
||||
logger = logging.getLogger('influxdb_client.client.write_api_async')
|
||||
|
||||
if _HAS_DATACLASS:
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class WriteApiAsync(_BaseWriteApi):
|
||||
"""
|
||||
Implementation for '/api/v2/write' endpoint.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from influxdb_client_async import InfluxDBClientAsync
|
||||
|
||||
|
||||
# Initialize async/await instance of Write API
|
||||
async with InfluxDBClientAsync(url="http://localhost:8086", token="my-token", org="my-org") as client:
|
||||
write_api = client.write_api()
|
||||
"""
|
||||
|
||||
def __init__(self, influxdb_client, point_settings: PointSettings = PointSettings()) -> None:
|
||||
"""
|
||||
Initialize defaults.
|
||||
|
||||
:param influxdb_client: with default settings (organization)
|
||||
:param point_settings: settings to store default tags.
|
||||
"""
|
||||
super().__init__(influxdb_client=influxdb_client, point_settings=point_settings)
|
||||
|
||||
async def write(self, bucket: str, org: str = None,
|
||||
record: Union[str, Iterable['str'], Point, Iterable['Point'], dict, Iterable['dict'], bytes,
|
||||
Iterable['bytes'], NamedTuple, Iterable['NamedTuple'], 'dataclass',
|
||||
Iterable['dataclass']] = None,
|
||||
write_precision: WritePrecision = DEFAULT_WRITE_PRECISION, **kwargs) -> bool:
|
||||
"""
|
||||
Write time-series data into InfluxDB.
|
||||
|
||||
:param str bucket: specifies the destination bucket for writes (required)
|
||||
:param str, Organization org: specifies the destination organization for writes;
|
||||
take the ID, Name or Organization.
|
||||
If not specified the default value from ``InfluxDBClientAsync.org`` is used.
|
||||
:param WritePrecision write_precision: specifies the precision for the unix timestamps within
|
||||
the body line-protocol. The precision specified on a Point has precedes
|
||||
and is use for write.
|
||||
:param record: Point, Line Protocol, Dictionary, NamedTuple, Data Classes, Pandas DataFrame
|
||||
:key data_frame_measurement_name: name of measurement for writing Pandas DataFrame - ``DataFrame``
|
||||
:key data_frame_tag_columns: list of DataFrame columns which are tags,
|
||||
rest columns will be fields - ``DataFrame``
|
||||
:key data_frame_timestamp_column: name of DataFrame column which contains a timestamp. The column can be defined as a :class:`~str` value
|
||||
formatted as `2018-10-26`, `2018-10-26 12:00`, `2018-10-26 12:00:00-05:00`
|
||||
or other formats and types supported by `pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_ - ``DataFrame``
|
||||
:key data_frame_timestamp_timezone: name of the timezone which is used for timestamp column - ``DataFrame``
|
||||
:key record_measurement_key: key of record with specified measurement -
|
||||
``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_measurement_name: static measurement name - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_time_key: key of record with specified timestamp - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_tag_keys: list of record keys to use as a tag - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:key record_field_keys: list of record keys to use as a field - ``dictionary``, ``NamedTuple``, ``dataclass``
|
||||
:return: ``True`` for successfully accepted data, otherwise raise an exception
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# Record as Line Protocol
|
||||
await write_api.write("my-bucket", "my-org", "h2o_feet,location=us-west level=125i 1")
|
||||
|
||||
# Record as Dictionary
|
||||
dictionary = {
|
||||
"measurement": "h2o_feet",
|
||||
"tags": {"location": "us-west"},
|
||||
"fields": {"level": 125},
|
||||
"time": 1
|
||||
}
|
||||
await write_api.write("my-bucket", "my-org", dictionary)
|
||||
|
||||
# Record as Point
|
||||
from influxdb_client import Point
|
||||
point = Point("h2o_feet").tag("location", "us-west").field("level", 125).time(1)
|
||||
await write_api.write("my-bucket", "my-org", point)
|
||||
|
||||
DataFrame:
|
||||
If the ``data_frame_timestamp_column`` is not specified the index of `Pandas DataFrame <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.html>`_
|
||||
is used as a ``timestamp`` for written data. The index can be `PeriodIndex <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.PeriodIndex.html#pandas.PeriodIndex>`_
|
||||
or its must be transformable to ``datetime`` by
|
||||
`pandas.to_datetime <https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html#pandas.to_datetime>`_.
|
||||
|
||||
If you would like to transform a column to ``PeriodIndex``, you can use something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pandas as pd
|
||||
|
||||
# DataFrame
|
||||
data_frame = ...
|
||||
# Set column as Index
|
||||
data_frame.set_index('column_name', inplace=True)
|
||||
# Transform index to PeriodIndex
|
||||
data_frame.index = pd.to_datetime(data_frame.index, unit='s')
|
||||
|
||||
""" # noqa: E501
|
||||
org = get_org_query_param(org=org, client=self._influxdb_client)
|
||||
self._append_default_tags(record)
|
||||
|
||||
payloads = defaultdict(list)
|
||||
self._serialize(record, write_precision, payloads, precision_from_point=True, **kwargs)
|
||||
|
||||
futures = []
|
||||
for payload_precision, payload_line in payloads.items():
|
||||
futures.append(ensure_future
|
||||
(self._write_service.post_write_async(org=org, bucket=bucket,
|
||||
body=b'\n'.join(payload_line),
|
||||
precision=payload_precision, async_req=False,
|
||||
_return_http_data_only=False,
|
||||
content_type="text/plain; charset=utf-8")))
|
||||
|
||||
results = await gather(*futures, return_exceptions=True)
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
|
||||
return False not in [re[1] in (201, 204) for re in results]
|
||||
@@ -1,283 +0,0 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""
|
||||
InfluxDB OSS API Service.
|
||||
|
||||
The InfluxDB v2 API provides a programmatic interface for all interactions with InfluxDB. Access the InfluxDB API using the `/api/v2/` endpoint. # noqa: E501
|
||||
|
||||
OpenAPI spec version: 2.0.0
|
||||
Generated by: https://openapi-generator.tech
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import multiprocessing
|
||||
import sys
|
||||
|
||||
import urllib3
|
||||
|
||||
|
||||
class TypeWithDefault(type):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, dct):
|
||||
"""Initialize with defaults."""
|
||||
super(TypeWithDefault, cls).__init__(name, bases, dct)
|
||||
cls._default = None
|
||||
|
||||
def __call__(cls):
|
||||
"""Call self as a function."""
|
||||
if cls._default is None:
|
||||
cls._default = type.__call__(cls)
|
||||
return copy.copy(cls._default)
|
||||
|
||||
def set_default(cls, default):
|
||||
"""Set dafaults."""
|
||||
cls._default = copy.copy(default)
|
||||
|
||||
|
||||
class Configuration(object, metaclass=TypeWithDefault):
|
||||
"""NOTE: This class is auto generated by OpenAPI Generator.
|
||||
|
||||
Ref: https://openapi-generator.tech
|
||||
Do not edit the class manually.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize configuration."""
|
||||
# Default Base url
|
||||
self.host = "http://localhost/api/v2"
|
||||
# Temp file folder for downloading files
|
||||
self.temp_folder_path = None
|
||||
|
||||
# Authentication Settings
|
||||
# dict to store API key(s)
|
||||
self.api_key = {}
|
||||
# dict to store API prefix (e.g. Bearer)
|
||||
self.api_key_prefix = {}
|
||||
# Username for HTTP basic authentication
|
||||
self.username = ""
|
||||
# Password for HTTP basic authentication
|
||||
self.password = ""
|
||||
|
||||
# Logging Settings
|
||||
self.loggers = {}
|
||||
# Log format
|
||||
self.logger_format = '%(asctime)s %(levelname)s %(message)s'
|
||||
# Log stream handler
|
||||
self.logger_stream_handler = None
|
||||
# Log file handler
|
||||
self.logger_file_handler = None
|
||||
# Debug file location
|
||||
self.logger_file = None
|
||||
# Debug switch
|
||||
self.debug = False
|
||||
|
||||
# SSL/TLS verification
|
||||
# Set this to false to skip verifying SSL certificate when calling API
|
||||
# from https server.
|
||||
self.verify_ssl = True
|
||||
# Set this to customize the certificate file to verify the peer.
|
||||
self.ssl_ca_cert = None
|
||||
# client certificate file
|
||||
self.cert_file = None
|
||||
# client key file
|
||||
self.cert_key_file = None
|
||||
# client key file password
|
||||
self.cert_key_password = None
|
||||
# Set this to True/False to enable/disable SSL hostname verification.
|
||||
self.assert_hostname = None
|
||||
|
||||
# Set this to specify a custom ssl context to inject this context inside the urllib3 connection pool.
|
||||
self.ssl_context = None
|
||||
|
||||
# urllib3 connection pool's maximum number of connections saved
|
||||
# per pool. urllib3 uses 1 connection as default value, but this is
|
||||
# not the best value when you are making a lot of possibly parallel
|
||||
# requests to the same host, which is often the case here.
|
||||
# cpu_count * 5 is used as default value to increase performance.
|
||||
self.connection_pool_maxsize = multiprocessing.cpu_count() * 5
|
||||
# Timeout setting for a request. If one number provided, it will be total request timeout.
|
||||
# It can also be a pair (tuple) of (connection, read) timeouts.
|
||||
self.timeout = None
|
||||
|
||||
# Set to True/False to enable basic authentication when using proxied InfluxDB 1.8.x with no auth-enabled
|
||||
self.auth_basic = False
|
||||
|
||||
# Proxy URL
|
||||
self.proxy = None
|
||||
# A dictionary containing headers that will be sent to the proxy
|
||||
self.proxy_headers = None
|
||||
# Safe chars for path_param
|
||||
self.safe_chars_for_path_param = ''
|
||||
|
||||
@property
|
||||
def logger_file(self):
|
||||
"""Logger file.
|
||||
|
||||
If the logger_file is None, then add stream handler and remove file
|
||||
handler. Otherwise, add file handler and remove stream handler.
|
||||
|
||||
:param value: The logger_file path.
|
||||
:type: str
|
||||
"""
|
||||
return self.__logger_file
|
||||
|
||||
@logger_file.setter
|
||||
def logger_file(self, value):
|
||||
"""Logger file.
|
||||
|
||||
If the logger_file is None, then add stream handler and remove file
|
||||
handler. Otherwise, add file handler and remove stream handler.
|
||||
|
||||
:param value: The logger_file path.
|
||||
:type: str
|
||||
"""
|
||||
self.__logger_file = value
|
||||
if self.__logger_file:
|
||||
# If set logging file,
|
||||
# then add file handler and remove stream handler.
|
||||
self.logger_file_handler = logging.FileHandler(self.__logger_file)
|
||||
self.logger_file_handler.setFormatter(self.logger_formatter)
|
||||
for _, logger in self.loggers.items():
|
||||
logger.addHandler(self.logger_file_handler)
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
"""Debug status.
|
||||
|
||||
:param value: The debug status, True or False.
|
||||
:type: bool
|
||||
"""
|
||||
return self.__debug
|
||||
|
||||
@debug.setter
|
||||
def debug(self, value):
|
||||
"""Debug status.
|
||||
|
||||
:param value: The debug status, True or False.
|
||||
:type: bool
|
||||
"""
|
||||
self.__debug = value
|
||||
if self.__debug:
|
||||
# if debug status is True, turn on debug logging
|
||||
for name, logger in self.loggers.items():
|
||||
logger.setLevel(logging.DEBUG)
|
||||
if name == 'influxdb_client.client.http':
|
||||
# makes sure to do not duplicate stdout handler
|
||||
if not any(map(lambda h: isinstance(h, logging.StreamHandler) and h.stream == sys.stdout,
|
||||
logger.handlers)):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
# we use 'influxdb_client.client.http' logger instead of this
|
||||
# httplib.HTTPConnection.debuglevel = 1
|
||||
else:
|
||||
# if debug status is False, turn off debug logging,
|
||||
# setting log level to default `logging.WARNING`
|
||||
for _, logger in self.loggers.items():
|
||||
logger.setLevel(logging.WARNING)
|
||||
# we use 'influxdb_client.client.http' logger instead of this
|
||||
# httplib.HTTPConnection.debuglevel = 0
|
||||
|
||||
@property
|
||||
def logger_format(self):
|
||||
"""Logger format.
|
||||
|
||||
The logger_formatter will be updated when sets logger_format.
|
||||
|
||||
:param value: The format string.
|
||||
:type: str
|
||||
"""
|
||||
return self.__logger_format
|
||||
|
||||
@logger_format.setter
|
||||
def logger_format(self, value):
|
||||
"""Logger format.
|
||||
|
||||
The logger_formatter will be updated when sets logger_format.
|
||||
|
||||
:param value: The format string.
|
||||
:type: str
|
||||
"""
|
||||
self.__logger_format = value
|
||||
self.logger_formatter = logging.Formatter(self.__logger_format)
|
||||
|
||||
def get_api_key_with_prefix(self, identifier):
|
||||
"""Get API key (with prefix if set).
|
||||
|
||||
:param identifier: The identifier of apiKey.
|
||||
:return: The token for api key authentication.
|
||||
"""
|
||||
if (self.api_key.get(identifier) and
|
||||
self.api_key_prefix.get(identifier)):
|
||||
return self.api_key_prefix[identifier] + ' ' + self.api_key[identifier] # noqa: E501
|
||||
elif self.api_key.get(identifier):
|
||||
return self.api_key[identifier]
|
||||
|
||||
def get_basic_auth_token(self):
|
||||
"""Get HTTP basic authentication header (string).
|
||||
|
||||
:return: The token for basic HTTP authentication.
|
||||
"""
|
||||
return urllib3.util.make_headers(
|
||||
basic_auth=self.username + ':' + self.password
|
||||
).get('authorization')
|
||||
|
||||
def auth_settings(self):
|
||||
"""Get Auth Settings dict for api client.
|
||||
|
||||
:return: The Auth Settings information dict.
|
||||
"""
|
||||
return {
|
||||
'BasicAuthentication':
|
||||
{
|
||||
'type': 'basic',
|
||||
'in': 'header',
|
||||
'key': 'Authorization',
|
||||
'value': self.get_basic_auth_token()
|
||||
},
|
||||
'TokenAuthentication':
|
||||
{
|
||||
'type': 'api_key',
|
||||
'in': 'header',
|
||||
'key': 'Authorization',
|
||||
'value': self.get_api_key_with_prefix('Authorization')
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
def to_debug_report(self):
|
||||
"""Get the essential information for debugging.
|
||||
|
||||
:return: The report for debugging.
|
||||
"""
|
||||
from influxdb_client import VERSION
|
||||
return "Python SDK Debug Report:\n"\
|
||||
"OS: {env}\n"\
|
||||
"Python Version: {pyversion}\n"\
|
||||
"Version of the API: 2.0.0\n"\
|
||||
"SDK Package Version: {client_version}".\
|
||||
format(env=sys.platform, pyversion=sys.version, client_version=VERSION)
|
||||
|
||||
def update_request_header_params(self, path: str, params: dict):
|
||||
"""Update header params based on custom settings.
|
||||
|
||||
:param path: Resource path
|
||||
:param params: Header parameters dict to be updated.
|
||||
"""
|
||||
pass
|
||||
|
||||
def update_request_body(self, path: str, body):
|
||||
"""Update http body based on custom settings.
|
||||
|
||||
:param path: Resource path
|
||||
:param body: Request body to be updated.
|
||||
:return: Updated body
|
||||
"""
|
||||
return body
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user