Refactored code. Changing SQL and CSV formats.
This commit is contained in:
24841
http.client
Normal file
24841
http.client
Normal file
File diff suppressed because it is too large
Load Diff
321
sc200-pull.py
Executable file
321
sc200-pull.py
Executable file
@@ -0,0 +1,321 @@
|
||||
#!/usr/bin/python3
|
||||
from netaddr import IPNetwork
|
||||
#from netaddr import IPAddress
|
||||
from xmlrpc.client import ServerProxy
|
||||
import sqlite3
|
||||
import socket
|
||||
import csv
|
||||
import queue
|
||||
import threading
|
||||
import sys
|
||||
import http.client
|
||||
|
||||
WORKERS = 10
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
Nets = [
|
||||
'10.250.48.0/27',
|
||||
'10.250.48.32/27',
|
||||
'10.250.48.64/27',
|
||||
'10.250.48.96/27',
|
||||
'10.250.48.128/27',
|
||||
'10.250.48.160/27',
|
||||
'10.250.49.0/27',
|
||||
'10.250.49.32/27',
|
||||
'10.250.49.64/27',
|
||||
'10.250.49.128/27',
|
||||
'10.250.49.160/27',
|
||||
'10.250.50.0/27',
|
||||
'10.250.50.32/27',
|
||||
'10.250.50.65/27',
|
||||
'10.250.50.96/27',
|
||||
'10.250.50.128/27',
|
||||
'10.250.50.160/27',
|
||||
'10.250.50.192/27',
|
||||
'10.250.50.224/27',
|
||||
'10.250.51.0/27',
|
||||
'10.250.51.32/27',
|
||||
'10.250.51.64/27',
|
||||
'10.250.51.96/27',
|
||||
'10.250.51.128/27',
|
||||
'10.250.52.0/27',
|
||||
'10.250.52.32/27',
|
||||
'10.250.52.64/27',
|
||||
'10.250.52.96/27',
|
||||
'10.250.52.128/27',
|
||||
'10.250.52.160/27',
|
||||
'10.250.52.192/27'
|
||||
]
|
||||
else:
|
||||
Nets = sys.argv[1:]
|
||||
|
||||
#Nets = [
|
||||
#'10.250.48.0/27']
|
||||
|
||||
#print((Nets))
|
||||
|
||||
Settings = [
|
||||
'Site-Name',
|
||||
'Site-Notes',
|
||||
'Serial-Number',
|
||||
'Float-Voltage',
|
||||
'Operating-Voltage',
|
||||
'Battery-Capacity',
|
||||
'AC-Rectifier-Current-Limit',
|
||||
'High-Float-Threshold',
|
||||
'Enable-Active-Voltage-Control',
|
||||
'Enable-Temperature-Compensation',
|
||||
'Temperature-Compensation-Slope',
|
||||
'Temperature-Compensation-Reference-Temperature',
|
||||
'Temperature-Compensation-Upper-Limit',
|
||||
'Temperature-Compensation-Lower-Limit',
|
||||
'Enable-Equalize',
|
||||
'Equalize-Voltage',
|
||||
'Equalize-Duration',
|
||||
'Enable-Fast-Charge',
|
||||
'Fast-Charge-Voltage',
|
||||
'Fast-Charge-Voltage-Threshold',
|
||||
'Fast-Charge-Ampere-Hour-Threshold',
|
||||
'Fast-Charge-Recharge-Percentage',
|
||||
'Fast-Charge-Maximum-Duration',
|
||||
'Fast-Charge-Ampere-Hour-Stop-Threshold',
|
||||
'Enable-Battery-Current-Limit',
|
||||
'BCL-Limit',
|
||||
'Enable-Battery-Test',
|
||||
'Battery-Test-Duration',
|
||||
'Battery-Test-Termination-Voltage',
|
||||
'LVD-Disconnect-Voltage:1',
|
||||
'LVD-Reconnect-Voltage:1',
|
||||
'Number-Of-Registered-Rectifiers',
|
||||
'Number-Of-Rectifiers-Failed'
|
||||
] + \
|
||||
['Alarm-Name:' + repr(x) for x in range(59)] + \
|
||||
['Alarm-Severity:' + repr(x) for x in range(59)]
|
||||
|
||||
|
||||
def Setup():
|
||||
db.execute('''CREATE TABLE SettingsValues (
|
||||
IP TEXT,
|
||||
SettingsID TEXT,
|
||||
Value TEXT)''')
|
||||
|
||||
#db.execute('''CREATE TABLE Settings (
|
||||
#ID INTEGER PRIMARY KEY,
|
||||
#Name TEXT UNIQUE)''')
|
||||
|
||||
#db.execute('''CREATE TABLE SC200 (
|
||||
#IP text PRIMARY KEY,
|
||||
#SiteName text,
|
||||
#SiteNotes text,
|
||||
#SerialNumber text,
|
||||
#FloatVoltage float,
|
||||
#OperatingVoltage float,
|
||||
#BatteryCapacity int,
|
||||
#ACRectifierCurrentLimit int,
|
||||
#HighFloatThreshold float,
|
||||
#EnableActiveVoltageControl bool,
|
||||
#EnableTemperatureCompensation bool,
|
||||
#TemperatureCompensationSlope float,
|
||||
#TemperatureCompensationReferenceTemperature float,
|
||||
#TemperatureCompensationUpperLimit float,
|
||||
#TemperatureCompensationLowerLimit float,
|
||||
#EnableEqualize bool,
|
||||
#EqualizeVoltage float,
|
||||
#EqualizeDuration int,
|
||||
#EnableFastCharge bool,
|
||||
#FastChargeVoltage float,
|
||||
#FastChargeVoltageThreshold float,
|
||||
#FastChargeAmpereHourThreshold int,
|
||||
#FastChargeRechargePercentage int,
|
||||
#FastChargeMaximumDuration int,
|
||||
#FastChargeAmpereHourStopThreshold int,
|
||||
#EnableBatterCurrentLimit bool,
|
||||
#BCLLimit int,
|
||||
#EnableBatterTest bool,
|
||||
#BatteryTestDuration int,
|
||||
#BatteryTestTerminationVoltage float,
|
||||
#LVDDisconnectVoltage float,
|
||||
#LVDReconnectVoltage float,
|
||||
#NumberOfRegisteredRectifiers int,
|
||||
#NumberOfRectifiersFailed int
|
||||
#)''')
|
||||
|
||||
#SQL = '''CREATE TABLE AlarmNames (
|
||||
#SiteIP text primary key'''
|
||||
#for x in range(59):
|
||||
#SQL = SQL + ', Alarm' + repr(x) + ' text'
|
||||
#SQL = SQL + ')'
|
||||
|
||||
#db.execute(SQL)
|
||||
|
||||
#SQL = '''CREATE TABLE AlarmSeverities (
|
||||
#SiteIP text primary key'''
|
||||
#for x in range(59):
|
||||
#SQL = SQL + ', Alarm' + repr(x) + ' int'
|
||||
#SQL = SQL + ')'
|
||||
|
||||
#db.execute(SQL)
|
||||
|
||||
|
||||
def GetStatusCode(host, path="/"):
|
||||
try:
|
||||
conn = http.client.HTTPConnection(host)
|
||||
conn.request("GET", path)
|
||||
return conn.getresponse().status
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
class Worker(threading.Thread):
|
||||
|
||||
def __init__(self, IPs, Results):
|
||||
self.__IPs = IPs
|
||||
self.__Results = Results
|
||||
self.parent = threading.current_thread()
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
retry = False
|
||||
socket.setdefaulttimeout(5)
|
||||
#print ('Started')
|
||||
while 1:
|
||||
IP = self.__IPs.get()
|
||||
#print((self.getName()))
|
||||
if IP is None:
|
||||
self.__Results.put(None)
|
||||
#print('Exiting')
|
||||
break
|
||||
|
||||
#s = socket.socket()
|
||||
#if s.connect_ex((str(IP), 80)) == 0:
|
||||
if GetStatusCode(str(IP), "/languages") == 200:
|
||||
print((self.getName() + ' processing: ' + str(IP)))
|
||||
#print(('Socket OK: %s' % IP))
|
||||
#print(('%s' % IP))
|
||||
while True:
|
||||
proxy = ServerProxy('http://%s/xmlrpc' % IP)
|
||||
try:
|
||||
Values = proxy.db.get(Settings)
|
||||
except socket.timeout:
|
||||
retry = True
|
||||
continue
|
||||
|
||||
if retry:
|
||||
retry = False
|
||||
else:
|
||||
break
|
||||
|
||||
if len(Values) > 0:
|
||||
Values.insert(0, str(IP))
|
||||
self.__Results.put(['SC200', Values])
|
||||
|
||||
#AlarmNames = ['Alarm-Name:' + repr(x) for x in range(59)]
|
||||
#while True:
|
||||
#try:
|
||||
#Values = proxy.db.get(AlarmNames)
|
||||
#except socket.timeout:
|
||||
#retry = True
|
||||
#continue
|
||||
|
||||
#if retry:
|
||||
#retry = False
|
||||
#else:
|
||||
#break
|
||||
|
||||
#Values.insert(0, str(IP))
|
||||
|
||||
#self.__Results.put(['AlarmNames', Values])
|
||||
|
||||
#AlarmSeverities = ['Alarm-Severity:' + repr(x)
|
||||
#for x in range(59)]
|
||||
#Values = proxy.db.get(AlarmSeverities)
|
||||
|
||||
#Values.insert(0, str(IP))
|
||||
|
||||
#self.__Results.put(['AlarmSeverities', Values])
|
||||
#s.close()
|
||||
else:
|
||||
print((self.getName() + ' skipping: ' + str(IP)))
|
||||
|
||||
|
||||
def Loader():
|
||||
Count = WORKERS
|
||||
c = db.cursor()
|
||||
|
||||
while 1:
|
||||
Row = Results.get()
|
||||
if Row is None:
|
||||
if Count > 1:
|
||||
Count -= 1
|
||||
print(Count)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
#print(('Loading: %s %s' % (Row[1][0], Row[0])))
|
||||
#SQL = 'INSERT INTO %s VALUES (' % Row[0]
|
||||
#for i in Row[1]:
|
||||
#SQL += '?,'
|
||||
#SQL = SQL[:-1]
|
||||
#SQL += ')'
|
||||
|
||||
#c.execute(SQL, Row[1])
|
||||
#print((list(zip(Settings, Row[1][1:]))))
|
||||
c.executemany('INSERT INTO SettingsValues VALUES ( \'%s\', ?, ?)'
|
||||
% Row[1][0],
|
||||
list(zip(Settings, Row[1][1:])))
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
def Output():
|
||||
c = db.cursor()
|
||||
with open('/tmp/sc200.csv', 'w', newline='') as outFile:
|
||||
csvWriter = csv.writer(outFile, delimiter=',', quotechar='"',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
c.execute('''SELECT * FROM SettingsValues ORDER BY IP''')
|
||||
csvWriter.writerow([col[0] for col in c.description])
|
||||
for row in c:
|
||||
csvWriter.writerow(row)
|
||||
outFile.close()
|
||||
|
||||
#with open('/tmp/sc200-alarms.csv', 'w', newline='') as outFile:
|
||||
#csvWriter = csv.writer(outFile, delimiter=',', quotechar='"',
|
||||
#quoting=csv.QUOTE_MINIMAL)
|
||||
#c.execute('''SELECT * FROM AlarmNames LIMIT 1''')
|
||||
#for row in c:
|
||||
#csvWriter.writerow(row)
|
||||
#c.execute('''SELECT * FROM AlarmSeverities''')
|
||||
#for row in c:
|
||||
#csvWriter.writerow(row)
|
||||
#outFile.close()
|
||||
|
||||
db = sqlite3.connect(':memory:')
|
||||
#db = sqlite3.connect('/tmp/sc200.db')
|
||||
|
||||
Setup()
|
||||
IPs = queue.Queue(0)
|
||||
Results = queue.Queue(0)
|
||||
|
||||
for i in range(WORKERS):
|
||||
Worker(IPs, Results).start()
|
||||
|
||||
for SubNet in Nets:
|
||||
Net = IPNetwork(SubNet)
|
||||
|
||||
for IP in Net:
|
||||
if ((IP != Net.network and IP != Net.broadcast) or
|
||||
Net.broadcast is None):
|
||||
#print(('Queued: ' + str(IP)))
|
||||
IPs.put(IP)
|
||||
|
||||
for i in range(WORKERS):
|
||||
IPs.put(None)
|
||||
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
sys.exit(0)
|
||||
|
||||
Loader()
|
||||
Output()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
302
sc200-push.sh
Executable file
302
sc200-push.sh
Executable file
@@ -0,0 +1,302 @@
|
||||
#!/usr/bin/python3
|
||||
from netaddr import IPNetwork
|
||||
#from netaddr import IPAddress
|
||||
from xmlrpc.client import ServerProxy
|
||||
import sqlite3
|
||||
import socket
|
||||
import csv
|
||||
import queue
|
||||
import threading
|
||||
import sys
|
||||
import http.client
|
||||
|
||||
WORKERS = 10
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
Nets = [
|
||||
'10.250.48.0/27',
|
||||
'10.250.48.32/27',
|
||||
'10.250.48.64/27',
|
||||
'10.250.48.96/27',
|
||||
'10.250.48.128/27',
|
||||
'10.250.48.160/27',
|
||||
'10.250.49.0/27',
|
||||
'10.250.49.32/27',
|
||||
'10.250.49.64/27',
|
||||
'10.250.49.128/27',
|
||||
'10.250.49.160/27',
|
||||
'10.250.50.0/27',
|
||||
'10.250.50.32/27',
|
||||
'10.250.50.65/27',
|
||||
'10.250.50.96/27',
|
||||
'10.250.50.128/27',
|
||||
'10.250.50.160/27',
|
||||
'10.250.50.192/27',
|
||||
'10.250.50.224/27',
|
||||
'10.250.51.0/27',
|
||||
'10.250.51.32/27',
|
||||
'10.250.51.64/27',
|
||||
'10.250.51.96/27',
|
||||
'10.250.51.128/27',
|
||||
'10.250.52.0/27',
|
||||
'10.250.52.32/27',
|
||||
'10.250.52.64/27',
|
||||
'10.250.52.96/27',
|
||||
'10.250.52.128/27',
|
||||
'10.250.52.160/27',
|
||||
'10.250.52.192/27'
|
||||
]
|
||||
else:
|
||||
Nets = sys.argv[1:]
|
||||
|
||||
#Nets = [
|
||||
#'10.250.48.0/27']
|
||||
|
||||
#print((Nets))
|
||||
|
||||
|
||||
def Setup():
|
||||
db.execute('''CREATE TABLE SC200 (
|
||||
IP text PRIMARY KEY,
|
||||
SiteName text,
|
||||
SiteNotes text,
|
||||
SerialNumber text,
|
||||
FloatVoltage float,
|
||||
OperatingVoltage float,
|
||||
BatteryCapacity int,
|
||||
ACRectifierCurrentLimit int,
|
||||
HighFloatThreshold float,
|
||||
EnableActiveVoltageControl bool,
|
||||
EnableTemperatureCompensation bool,
|
||||
TemperatureCompensationSlope float,
|
||||
TemperatureCompensationReferenceTemperature float,
|
||||
TemperatureCompensationUpperLimit float,
|
||||
TemperatureCompensationLowerLimit float,
|
||||
EnableEqualize bool,
|
||||
EqualizeVoltage float,
|
||||
EqualizeDuration int,
|
||||
EnableFastCharge bool,
|
||||
FastChargeVoltage float,
|
||||
FastChargeVoltageThreshold float,
|
||||
FastChargeAmpereHourThreshold int,
|
||||
FastChargeRechargePercentage int,
|
||||
FastChargeMaximumDuration int,
|
||||
FastChargeAmpereHourStopThreshold int,
|
||||
EnableBatterCurrentLimit bool,
|
||||
BCLLimit int,
|
||||
EnableBatterTest bool,
|
||||
BatteryTestDuration int,
|
||||
BatteryTestTerminationVoltage float,
|
||||
LVDDisconnectVoltage float,
|
||||
LVDReconnectVoltage float,
|
||||
NumberOfRegisteredRectifiers int,
|
||||
NumberOfRectifiersFailed int
|
||||
)''')
|
||||
|
||||
SQL = '''CREATE TABLE AlarmNames (
|
||||
SiteIP text primary key'''
|
||||
for x in range(59):
|
||||
SQL = SQL + ', Alarm' + repr(x) + ' text'
|
||||
SQL = SQL + ')'
|
||||
|
||||
db.execute(SQL)
|
||||
|
||||
SQL = '''CREATE TABLE AlarmSeverities (
|
||||
SiteIP text primary key'''
|
||||
for x in range(59):
|
||||
SQL = SQL + ', Alarm' + repr(x) + ' int'
|
||||
SQL = SQL + ')'
|
||||
|
||||
db.execute(SQL)
|
||||
|
||||
|
||||
def GetStatusCode(host, path="/"):
|
||||
try:
|
||||
conn = http.client.HTTPConnection(host)
|
||||
conn.request("GET", path)
|
||||
return conn.getresponse().status
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
class Worker(threading.Thread):
|
||||
|
||||
def __init__(self, IPs, Results):
|
||||
self.__IPs = IPs
|
||||
self.__Results = Results
|
||||
self.parent = threading.current_thread()
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
retry = False
|
||||
socket.setdefaulttimeout(5)
|
||||
#print ('Started')
|
||||
while 1:
|
||||
IP = self.__IPs.get()
|
||||
#print((self.getName()))
|
||||
if IP is None:
|
||||
self.__Results.put(None)
|
||||
#print('Exiting')
|
||||
break
|
||||
|
||||
#s = socket.socket()
|
||||
#if s.connect_ex((str(IP), 80)) == 0:
|
||||
if GetStatusCode(str(IP), "/languages") == 200:
|
||||
print((self.getName() + ' processing: ' + str(IP)))
|
||||
#print(('Socket OK: %s' % IP))
|
||||
#print(('%s' % IP))
|
||||
while True:
|
||||
proxy = ServerProxy('http://%s/xmlrpc' % IP)
|
||||
try:
|
||||
Values = proxy.db.get([
|
||||
'Site-Name',
|
||||
'Site-Notes',
|
||||
'Serial-Number',
|
||||
'Float-Voltage',
|
||||
'Operating-Voltage',
|
||||
'Battery-Capacity',
|
||||
'AC-Rectifier-Current-Limit',
|
||||
'High-Float-Threshold',
|
||||
'Enable-Active-Voltage-Control',
|
||||
'Enable-Temperature-Compensation',
|
||||
'Temperature-Compensation-Slope',
|
||||
'Temperature-Compensation-Reference-Temperature',
|
||||
'Temperature-Compensation-Upper-Limit',
|
||||
'Temperature-Compensation-Lower-Limit',
|
||||
'Enable-Equalize',
|
||||
'Equalize-Voltage',
|
||||
'Equalize-Duration',
|
||||
'Enable-Fast-Charge',
|
||||
'Fast-Charge-Voltage',
|
||||
'Fast-Charge-Voltage-Threshold',
|
||||
'Fast-Charge-Ampere-Hour-Threshold',
|
||||
'Fast-Charge-Recharge-Percentage',
|
||||
'Fast-Charge-Maximum-Duration',
|
||||
'Fast-Charge-Ampere-Hour-Stop-Threshold',
|
||||
'Enable-Battery-Current-Limit',
|
||||
'BCL-Limit',
|
||||
'Enable-Battery-Test',
|
||||
'Battery-Test-Duration',
|
||||
'Battery-Test-Termination-Voltage',
|
||||
'LVD-Disconnect-Voltage:1',
|
||||
'LVD-Reconnect-Voltage:1',
|
||||
'Number-Of-Registered-Rectifiers',
|
||||
'Number-Of-Rectifiers-Failed'
|
||||
])
|
||||
except socket.timeout:
|
||||
retry = True
|
||||
continue
|
||||
|
||||
if retry:
|
||||
retry = False
|
||||
else:
|
||||
break
|
||||
|
||||
if len(Values) > 0:
|
||||
Values.insert(0, str(IP))
|
||||
self.__Results.put(['SC200', Values])
|
||||
|
||||
AlarmNames = ['Alarm-Name:' + repr(x) for x in range(59)]
|
||||
while True:
|
||||
try:
|
||||
Values = proxy.db.get(AlarmNames)
|
||||
except socket.timeout:
|
||||
retry = True
|
||||
continue
|
||||
|
||||
if retry:
|
||||
retry = False
|
||||
else:
|
||||
break
|
||||
|
||||
Values.insert(0, str(IP))
|
||||
|
||||
self.__Results.put(['AlarmNames', Values])
|
||||
|
||||
AlarmSeverities = ['Alarm-Severity:' + repr(x)
|
||||
for x in range(59)]
|
||||
Values = proxy.db.get(AlarmSeverities)
|
||||
|
||||
Values.insert(0, str(IP))
|
||||
|
||||
self.__Results.put(['AlarmSeverities', Values])
|
||||
#s.close()
|
||||
else:
|
||||
print((self.getName() + ' skipping: ' + str(IP)))
|
||||
|
||||
|
||||
def Loader():
|
||||
Count = WORKERS
|
||||
c = db.cursor()
|
||||
|
||||
while 1:
|
||||
Row = Results.get()
|
||||
if Row is None:
|
||||
if Count > 1:
|
||||
Count -= 1
|
||||
print(Count)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
print(('Loading: %s %s' % (Row[1][0], Row[0])))
|
||||
SQL = 'INSERT INTO %s VALUES (' % Row[0]
|
||||
for i in Row[1]:
|
||||
SQL += '?,'
|
||||
SQL = SQL[:-1]
|
||||
SQL += ')'
|
||||
|
||||
c.execute(SQL, Row[1])
|
||||
db.commit()
|
||||
|
||||
|
||||
def Output():
|
||||
c = db.cursor()
|
||||
with open('/tmp/sc200.csv', 'w', newline='') as outFile:
|
||||
csvWriter = csv.writer(outFile, delimiter=',', quotechar='"',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
c.execute('''SELECT * FROM SC200 ORDER BY IP''')
|
||||
csvWriter.writerow([col[0] for col in c.description])
|
||||
for row in c:
|
||||
csvWriter.writerow(row)
|
||||
outFile.close()
|
||||
|
||||
with open('/tmp/sc200-alarms.csv', 'w', newline='') as outFile:
|
||||
csvWriter = csv.writer(outFile, delimiter=',', quotechar='"',
|
||||
quoting=csv.QUOTE_MINIMAL)
|
||||
c.execute('''SELECT * FROM AlarmNames LIMIT 1''')
|
||||
for row in c:
|
||||
csvWriter.writerow(row)
|
||||
c.execute('''SELECT * FROM AlarmSeverities''')
|
||||
for row in c:
|
||||
csvWriter.writerow(row)
|
||||
outFile.close()
|
||||
|
||||
db = sqlite3.connect(':memory:')
|
||||
#db = sqlite3.connect('/tmp/sc200.db')
|
||||
|
||||
Setup()
|
||||
IPs = queue.Queue(0)
|
||||
Results = queue.Queue(0)
|
||||
|
||||
for i in range(WORKERS):
|
||||
Worker(IPs, Results).start()
|
||||
|
||||
for SubNet in Nets:
|
||||
Net = IPNetwork(SubNet)
|
||||
|
||||
for IP in Net:
|
||||
if IP != Net.network and IP != Net.broadcast:
|
||||
#print(('Queued: ' + str(IP)))
|
||||
IPs.put(IP)
|
||||
|
||||
for i in range(WORKERS):
|
||||
IPs.put(None)
|
||||
|
||||
if threading.current_thread() is not threading.main_thread():
|
||||
sys.exit(0)
|
||||
|
||||
Loader()
|
||||
Output()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"mainFile": "sc200.py",
|
||||
"mainFile": "sc200-pull.py",
|
||||
"use-tabs": false,
|
||||
"venv": "",
|
||||
"relatedProjects": [],
|
||||
@@ -26,4 +26,4 @@
|
||||
"project-type": "Import from sources",
|
||||
"postExecScript": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user