Commit c62705cc authored by Dmitry Volodin's avatar Dmitry Volodin Committed by Andrey Vertiprahov
Browse files

py3: alnum_key() for reliable sorting

parent 810f6297
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# Interface profile management
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -16,7 +16,7 @@ from noc.inv.models.interface import Interface
from noc.inv.models.interfaceprofile import InterfaceProfile
from noc.inv.models.interfaceclassificationrule import InterfaceClassificationRule
from noc.sa.models.managedobjectselector import ManagedObjectSelector
from noc.core.text import split_alnum
from noc.core.text import alnum_key
class Command(BaseCommand):
......@@ -60,7 +60,7 @@ class Command(BaseCommand):
@staticmethod
def get_interfaces(mo):
return sorted(
Interface.objects.filter(managed_object=mo.id, type="physical"), key=split_alnum
Interface.objects.filter(managed_object=mo.id, type="physical"), key=alnum_key
)
@staticmethod
......
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# ./noc nri
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -16,7 +16,7 @@ from noc.core.mongo.connection import connect
from noc.inv.models.interface import Interface
from noc.sa.models.managedobjectselector import ManagedObjectSelector
from noc.core.etl.portmapper.loader import loader
from noc.core.text import split_alnum, format_table
from noc.core.text import alnum_key, format_table
class Command(BaseCommand):
......@@ -63,8 +63,8 @@ class Command(BaseCommand):
self.print(ln, rn, i.get("nri_name"))
status = "Failed to convert to local name"
r += [(i["name"], rn, i.get("nri_name", "--"), status)]
r = [("Local", "Remote", "Interface NRI", "Status")] + sorted(
r, key=lambda x: split_alnum(x[0])
r = [("Local", "Remote", "Interface NRI", "Status")] + list(
sorted(r, key=lambda x: alnum_key(x[0]))
)
self.stdout.write("%s\n" % format_table([0, 0, 0, 0], r, sep=" | ", hsep="-+-"))
......
......@@ -13,6 +13,7 @@ import re
import six
from six.moves import zip_longest
from numpy import array
from typing import List, Union, Iterable
# NOC modules
from noc.core.comp import bord
......@@ -372,7 +373,23 @@ def indent(text, n=4):
return i + text.replace("\n", "\n" + i)
rx_split_alnum = re.compile(r"(\d+|[^0-9]+)")
def _iter_split_alnum(s):
# type: (six.string_types) -> Iterable[six.string_types]
"""
Iterator yielding alphabetic and numeric sections if string
:param s:
:return:
"""
for match in rx_split_alnum.finditer(s):
yield match.group(0)
def split_alnum(s):
# type: (six.string_types) -> List[Union[six.string_types, int]]
"""
Split line to a sequence of iterating alpha and digit strings
......@@ -391,22 +408,32 @@ def split_alnum(s):
['ge-', 1, '/', 0, '/', 1, '.', 15]
"""
def convert(x):
def maybe_int(v):
# type: (six.string_types) -> Union[six.string_types, int]
try:
return int(x)
return int(v)
except ValueError:
return x
return v
r = []
digit = None
for c in s:
d = c.isdigit()
if d != digit:
digit = d
r += [c]
else:
r[-1] += c
return [convert(x) for x in r]
return [maybe_int(x) for x in _iter_split_alnum(s)]
def alnum_key(s):
# type: (six.string_types) -> six.string_types
"""
Comparable alpha-numeric key
:param s:
:return:
"""
def maybe_formatted_int(v):
# type: (six.string_types) -> six.string_types
try:
return "%012d" % int(v)
except ValueError:
return v
return "".join(maybe_formatted_int(x) for x in _iter_split_alnum(s))
rx_notspace = re.compile(r"^\S+")
......
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# BaseTopology class
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -17,7 +17,7 @@ import cachetools
# NOC modules
from noc.core.stencil import stencil_registry
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from .layout.ring import RingLayout
from .layout.spring import SpringLayout
from .layout.tree import TreeLayout
......@@ -173,13 +173,13 @@ class BaseTopology(object):
id_to_name = {}
dl_map = {} # downlink -> uplink port
for p in self.G.node[uplink]["ports"]:
id_to_name[p["id"]] = sorted(p["ports"], key=split_alnum)[0]
id_to_name[p["id"]] = sorted(p["ports"], key=alnum_key)[0]
for dl in downlinks:
for p in self.G.edges[uplink, dl]["ports"]:
if p in id_to_name:
dl_map[dl] = id_to_name[p]
break
return sorted(dl_map, key=lambda x: split_alnum(dl_map[x]))
return sorted(dl_map, key=lambda x: alnum_key(dl_map[x]))
@cachetools.cachedmethod(operator.attrgetter("_rings_cache"))
def get_rings(self):
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# Firmware Policy
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -18,7 +18,7 @@ from noc.sa.models.managedobjectprofile import ManagedObjectProfile
from .firmware import Firmware
from .platform import Platform
from noc.core.mongo.fields import ForeignKeyField, PlainReferenceField
from noc.core.text import split_alnum
from noc.core.text import alnum_key
FS_RECOMMENDED = "r"
......@@ -85,5 +85,5 @@ class FirmwarePolicy(Document):
versions += [fp.firmware.version]
if versions:
# Get latest acceptable version
return sorted(versions, key=lambda x: split_alnum(x))[-1]
return list(sorted(versions, key=lambda x: alnum_key(x)))[-1]
return None
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# interfacepath card
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -26,7 +26,7 @@ from noc.core.topology.constraint.upwards import UpwardsConstraint
from noc.core.topology.constraint.vlan import VLANConstraint
from noc.core.topology.goal.level import ManagedObjectLevelGoal
from noc.inv.models.subinterface import SubInterface
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from noc.core.bi.decorator import bi_hash
from noc.core.clickhouse.connect import connection as ch_connection
from noc.core.clickhouse.error import ClickhouseError
......@@ -137,7 +137,7 @@ class InterfacePathCard(BaseCard):
"""
ingress = [] # type: List[Interface]
egress = [] # type: List[Interface]
for iface in sorted(interfaces, key=lambda x: split_alnum(x.name)):
for iface in sorted(interfaces, key=lambda x: alnum_key(x.name)):
if iface.managed_object == obj:
egress += [iface]
else:
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# ManagedObject card handler
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -32,7 +32,7 @@ from noc.inv.models.link import Link
from noc.sa.models.service import Service
from noc.inv.models.firmwarepolicy import FirmwarePolicy
from noc.sa.models.servicesummary import ServiceSummary
from noc.core.text import split_alnum, list_to_ranges
from noc.core.text import alnum_key, list_to_ranges
from noc.maintenance.models.maintenance import Maintenance
from noc.sa.models.useraccess import UserAccess
from noc.core.pm.utils import get_interface_metrics, get_objects_metrics
......@@ -161,18 +161,18 @@ class ManagedObjectCard(BaseCard):
"id": l.id,
"role": role,
"local_interface": sorted(
local_interfaces, key=lambda x: split_alnum(x.name)
local_interfaces, key=lambda x: alnum_key(x.name)
),
"remote_object": ro,
"remote_interface": sorted(
remote_interfaces, key=lambda x: split_alnum(x.name)
remote_interfaces, key=lambda x: alnum_key(x.name)
),
"remote_status": "up" if ro.get_status() else "down",
}
]
links = sorted(
links,
key=lambda x: (x["role"] != "uplink", split_alnum(x["local_interface"][0].name)),
key=lambda x: (x["role"] != "uplink", alnum_key(x["local_interface"][0].name)),
)
# Build global services summary
service_summary = ServiceSummary.get_object_summary(self.object)
......@@ -257,7 +257,7 @@ class ManagedObjectCard(BaseCard):
si = si[0]
interfaces[-1]["untagged_vlan"] = si.untagged_vlan
interfaces[-1]["tagged_vlans"] = list_to_ranges(si.tagged_vlans).replace(",", ", ")
interfaces = sorted(interfaces, key=lambda x: split_alnum(x["name"]))
interfaces = sorted(interfaces, key=lambda x: alnum_key(x["name"]))
# Resource groups
# Service groups (i.e. server)
static_services = set(self.object.static_service_groups)
......
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# managedobject datastream
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -25,7 +25,7 @@ from noc.inv.models.interface import Interface
from noc.inv.models.subinterface import SubInterface
from noc.inv.models.link import Link
from noc.inv.models.discoveryid import DiscoveryID
from noc.core.text import split_alnum
from noc.core.text import alnum_key
def qs(s):
......@@ -184,7 +184,7 @@ class ManagedObjectDataStream(DataStream):
# Get interfaces
interfaces = sorted(
Interface._get_collection().find({"managed_object": mo.id}),
key=lambda x: split_alnum(x["name"]),
key=lambda x: alnum_key(x["name"]),
)
# Populate cache
for i in interfaces:
......@@ -231,7 +231,7 @@ class ManagedObjectDataStream(DataStream):
# Apply subinterfaces
r["subinterfaces"] = [
ManagedObjectDataStream._get_subinterface(s)
for s in sorted(subs, key=lambda x: split_alnum(x["name"]))
for s in sorted(subs, key=lambda x: alnum_key(x["name"]))
]
# Apply links
if links:
......
......@@ -2,7 +2,7 @@
# ----------------------------------------------------------------------
# path API
# ----------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ----------------------------------------------------------------------
......@@ -41,7 +41,7 @@ from noc.core.topology.constraint.any import AnyConstraint
from noc.core.topology.goal.base import BaseGoal
from noc.core.topology.goal.managedobject import ManagedObjectGoal
from noc.core.topology.goal.level import ManagedObjectLevelGoal
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from ..base import NBIAPI
# Constants
......@@ -285,7 +285,7 @@ class PathAPI(NBIAPI):
"address": obj.address,
"bi_id": obj.bi_id,
},
"interfaces": list(sorted(objects[obj], key=split_alnum)),
"interfaces": list(sorted(objects[obj], key=alnum_key)),
}
for obj in order
]
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# inv.interface application
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -24,7 +24,7 @@ from noc.sa.interfaces.base import (
from noc.main.models.resourcestate import ResourceState
from noc.project.models.project import Project
from noc.vc.models.vcdomain import VCDomain
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from noc.core.translation import ugettext as _
from noc.config import config
from noc.core.comp import smart_text
......@@ -55,7 +55,7 @@ class InterfaceAppplication(ExtApplication):
"""
def sorted_iname(s):
return sorted(s, key=lambda x: split_alnum(x["name"]))
return list(sorted(s, key=lambda x: alnum_key(x["name"])))
def get_style(i):
profile = i.profile
......@@ -222,7 +222,7 @@ class InterfaceAppplication(ExtApplication):
for i in Interface.objects.filter(managed_object=o.id, type="physical").order_by("name")
if not i.link
]
return sorted(r, key=lambda x: split_alnum(x["label"]))
return list(sorted(r, key=lambda x: alnum_key(x["label"])))
@view(
url=r"^l1/(?P<iface_id>[0-9a-f]{24})/change_profile/$",
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# inv.map application
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -27,7 +27,7 @@ from noc.fm.models.activealarm import ActiveAlarm
from noc.core.topology.segment import SegmentTopology
from noc.inv.models.discoveryid import DiscoveryID
from noc.maintenance.models.maintenance import Maintenance
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from noc.core.pm.utils import get_interface_metrics
from noc.sa.interfaces.base import (
ListOfParameter,
......@@ -215,7 +215,7 @@ class MapApplication(ExtApplication):
"name": mo.name,
"interfaces": [
{"name": i.name, "description": i.description or None, "status": i.status}
for i in sorted(o[mo], key=lambda x: split_alnum(x.name))
for i in sorted(o[mo], key=lambda x: alnum_key(x.name))
],
}
]
......@@ -277,7 +277,7 @@ class MapApplication(ExtApplication):
"name": mo.name,
"interfaces": [
{"name": i.name, "description": i.description or None, "status": i.status}
for i in sorted(o[mo], key=lambda x: split_alnum(x.name))
for i in sorted(o[mo], key=lambda x: alnum_key(x.name))
],
}
]
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# ManagedObject's dynamic dashboard
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -19,7 +19,7 @@ from .base import BaseDashboard
from noc.config import config
from noc.inv.models.interface import Interface
from noc.inv.models.subinterface import SubInterface
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from noc.pm.models.metrictype import MetricType
from noc.sa.models.managedobject import ManagedObject
......@@ -83,7 +83,7 @@ class MODashboard(BaseDashboard):
ifaces = [i for i in all_ifaces if i.profile == profile]
ports = []
radio = []
for iface in sorted(ifaces, key=lambda el: split_alnum(el.name)):
for iface in sorted(ifaces, key=lambda el: alnum_key(el.name)):
if iface.type == "SVI" and not iface.profile.allow_subinterface_metrics:
continue
if iface.type == "aggregated" and iface.lag_members:
......
......@@ -2,7 +2,7 @@
# ---------------------------------------------------------------------
# sa.managedobject application
# ---------------------------------------------------------------------
# Copyright (C) 2007-2019 The NOC Project
# Copyright (C) 2007-2020 The NOC Project
# See LICENSE for details
# ---------------------------------------------------------------------
......@@ -37,7 +37,7 @@ from noc.main.models.resourcestate import ResourceState
from noc.project.models.project import Project
from noc.vc.models.vcdomain import VCDomain
from noc.sa.models.objectcapabilities import ObjectCapabilities
from noc.core.text import split_alnum
from noc.core.text import alnum_key
from noc.sa.interfaces.base import (
ListOfParameter,
ModelParameter,
......@@ -386,7 +386,7 @@ class ManagedObjectApplication(ExtModelApplication):
"""
def sorted_iname(s):
return sorted(s, key=lambda x: split_alnum(x["name"]))
return list(sorted(s, key=lambda x: alnum_key(x["name"])))
def get_style(i):
profile = i.profile
......@@ -911,7 +911,7 @@ class ManagedObjectApplication(ExtModelApplication):
"""
def sorted_iname(s):
return sorted(s, key=lambda x: split_alnum(x["name"]))
return list(sorted(s, key=lambda x: alnum_key(x["name"])))
# Get object
o = self.get_object_or_404(ManagedObject, id=int(id))
......
......@@ -19,6 +19,7 @@ from noc.core.text import (
replace_re_group,
indent,
split_alnum,
alnum_key,
find_indented,
to_seconds,
format_table,
......@@ -398,6 +399,20 @@ def test_split_alnum(config, expected):
assert split_alnum(config) == expected
@pytest.mark.parametrize(
"input, expected",
[
("auto", "auto"),
("0", "000000000000"),
("1", "000000000001"),
("Fa 0/1", "Fa 000000000000/000000000001"),
("ge-1/0/1.15", "ge-000000000001/000000000000/000000000001.000000000015"),
],
)
def test_alnum_key(input, expected):
assert alnum_key(input) == expected
@pytest.mark.parametrize(
"config, expected",
[
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment