Skip to content
/ noc Public

Commit a80cc4c

Browse files
authored
GetJsonPath protocol (#37)
1 parent 56289e0 commit a80cc4c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+580
-314
lines changed

bi/models/dashboard.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# ----------------------------------------------------------------------
22
# Dashboard storage
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2020 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

88
# Python modules
99
import datetime
1010
from base64 import b85encode
11+
from pathlib import Path
1112

1213
# Third-party modules
1314
from mongoengine.document import Document, EmbeddedDocument
@@ -27,6 +28,7 @@
2728
from noc.core.mongo.fields import ForeignKeyField
2829
from noc.core.prettyjson import to_json
2930
from noc.core.comp import smart_text
31+
from noc.core.path import safe_json_path
3032

3133
DAL_NONE = -1
3234
DAL_RO = 0
@@ -174,5 +176,5 @@ def to_json(self) -> str:
174176
order=["title", "uuid", "description", "created"],
175177
)
176178

177-
def get_json_path(self) -> str:
178-
return "%s.json" % self.uuid
179+
def get_json_path(self) -> Path:
180+
return safe_json_path(str(self.uuid))

bi/models/dashboardlayout.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
# ----------------------------------------------------------------------
22
# Dashboard Layout
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2020 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

8+
# Python modules
9+
from pathlib import Path
10+
811
# Third-party modules
912
from mongoengine.document import Document, EmbeddedDocument
1013
from mongoengine.fields import StringField, UUIDField, IntField, ListField, EmbeddedDocumentField
1114

1215
# NOC modules
1316
from noc.core.prettyjson import to_json
17+
from noc.core.path import safe_json_path
1418

1519

1620
class DashboardCell(EmbeddedDocument):
@@ -72,5 +76,5 @@ def to_json(self) -> str:
7276
order=["name", "uuid", "description", "cells"],
7377
)
7478

75-
def get_json_path(self) -> str:
76-
return "%s.json" % self.name
79+
def get_json_path(self) -> Path:
80+
return safe_json_path(self.name)

cm/models/confdbquery.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# ----------------------------------------------------------------------
22
# ConfDBQuery model
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2021 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

88
# Python modules
99
import threading
1010
from typing import Optional, Union
1111
import operator
12-
import os
12+
from pathlib import Path
1313

1414
# Third-party modules
1515
from bson import ObjectId
@@ -26,9 +26,9 @@
2626
# NOC modules
2727
from noc.core.ip import IP
2828
from noc.core.prettyjson import to_json
29-
from noc.core.text import quote_safe_path
3029
from noc.core.model.decorator import on_delete_check
3130
from noc.sa.interfaces.base import StringParameter, IntParameter, BooleanParameter
31+
from noc.core.path import safe_json_path
3232

3333

3434
class IPParameter(object):
@@ -109,9 +109,8 @@ def __str__(self):
109109
def get_by_id(cls, oid: Union[str, ObjectId]) -> Optional["ConfDBQuery"]:
110110
return ConfDBQuery.objects.filter(id=oid).first()
111111

112-
def get_json_path(self) -> str:
113-
p = [quote_safe_path(n.strip()) for n in self.name.split("|")]
114-
return os.path.join(*p) + ".json"
112+
def get_json_path(self) -> Path:
113+
return safe_json_path(self.name)
115114

116115
def query(self, engine, **kwargs):
117116
"""

cm/models/configurationparam.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# ----------------------------------------------------------------------
22
# ConfigurationParam model
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2023 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

88
# Python modules
99
import threading
1010
import operator
11-
import os
1211
import re
1312
from dataclasses import dataclass
1413
from typing import List, Optional, Union, NoReturn, Any
14+
from pathlib import Path
1515

1616
# Third-party modules
1717
from bson import ObjectId
@@ -28,9 +28,9 @@
2828
# NOC modules
2929
from noc.core.mongo.fields import PlainReferenceField
3030
from noc.core.prettyjson import to_json
31-
from noc.core.text import quote_safe_path
3231
from noc.core.model.decorator import on_delete_check
3332
from noc.pm.models.metrictype import MetricType
33+
from noc.core.path import safe_json_path
3434
from .configurationscope import ConfigurationScope
3535

3636

@@ -271,9 +271,8 @@ def get_by_id(cls, oid: Union[str, ObjectId]) -> Optional["ConfigurationParam"]:
271271
def get_by_code(cls, code) -> Optional["ConfigurationParam"]:
272272
return ConfigurationParam.objects.filter(code=code).first()
273273

274-
def get_json_path(self) -> str:
275-
p = [quote_safe_path(n.strip()) for n in self.name.split("|")]
276-
return os.path.join(*p) + ".json"
274+
def get_json_path(self) -> Path:
275+
return safe_json_path(self.name)
277276

278277
def to_json(self) -> str:
279278
r = {

cm/models/configurationscope.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# ----------------------------------------------------------------------
22
# Configuration Scope model
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2023 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

88
# Python modules
99
import threading
1010
from typing import Optional, Union
1111
import operator
12-
import os
12+
from pathlib import Path
1313

1414
# Third-party modules
1515
from bson import ObjectId
@@ -22,8 +22,8 @@
2222

2323
# NOC modules
2424
from noc.core.prettyjson import to_json
25-
from noc.core.text import quote_safe_path
2625
from noc.core.model.decorator import on_delete_check
26+
from noc.core.path import safe_json_path
2727

2828

2929
id_lock = threading.Lock()
@@ -63,9 +63,8 @@ def get_by_id(cls, oid: Union[str, ObjectId]) -> Optional["ConfigurationScope"]:
6363
def get_by_name(cls, name: str) -> Optional["ConfigurationScope"]:
6464
return ConfigurationScope.objects.filter(name=name).first()
6565

66-
def get_json_path(self) -> str:
67-
p = [quote_safe_path(n.strip()) for n in self.name.split("|")]
68-
return os.path.join(*p) + ".json"
66+
def get_json_path(self) -> Path:
67+
return safe_json_path(self.name)
6968

7069
@property
7170
def is_common(self) -> bool:

commands/collection.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# ----------------------------------------------------------------------
22
# Collections manipulation
33
# ----------------------------------------------------------------------
4-
# Copyright (C) 2007-2019 The NOC Project
4+
# Copyright (C) 2007-2025 The NOC Project
55
# See LICENSE for details
66
# ----------------------------------------------------------------------
77

88
# Python modules
99
import os
1010
import argparse
11+
from pathlib import Path
1112

1213
# Third-party modules
1314
import orjson
@@ -127,20 +128,20 @@ def handle_export(
127128
if list_collection is not None:
128129
if list_collection is True:
129130
for c in Collection.iter_collections():
130-
print("%s" % c.name, file=self.stdout)
131+
print(c.name, file=self.stdout)
131132
else:
132133
if list_collection not in MODELS:
133134
print("Collection not found", file=self.stdout)
134135
return
135136
objs = MODELS[list_collection].objects.all().order_by("name")
136137
for o in objs:
137-
print('uuid:%s name:"%s"' % (o.uuid, o.name), file=self.stdout)
138+
print(f'uuid:{o.uuid} name:"{o.name}"', file=self.stdout)
138139
else:
139140
if not export_path or not export_collections:
140141
return
141-
if not os.path.isdir(export_path):
142-
self.die("Path not found: %s" % export_path)
143-
142+
root = Path(export_path)
143+
if not root.is_dir():
144+
self.die(f"Path not found: {export_path}")
144145
for ecname in export_collections:
145146
if ecname not in MODELS:
146147
print("Collection not found", file=self.stdout)
@@ -152,8 +153,9 @@ def handle_export(
152153
kwargs["uuid__in"] = export_model_uuids
153154
objs = MODELS[ecname].objects.filter(**kwargs).order_by("name")
154155
for o in objs:
155-
path = os.path.join(export_path, ecname, o.get_json_path())
156-
print('export "%s" to %s' % (getattr(o, "name", o), path), file=self.stdout)
156+
path = root / ecname / o.get_json_path()
157+
x_name = getattr(o, "name", o)
158+
print(f'export "{x_name}" to {path}', file=self.stdout)
157159
safe_rewrite(path, o.to_json(), mode=0o644)
158160

159161

core/collection/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
# ----------------------------------------------------------------------
77

88
# Python modules
9-
import os
109
import zlib
1110
import csv
1211
import shutil
@@ -479,7 +478,7 @@ def install(cls, data):
479478
# Format JSON
480479
json_data = o.to_json()
481480
# Write
482-
path = os.path.join(cls.PREFIX, c.name, o.get_json_path())
481+
path = Path(cls.PREFIX, c.name) / o.get_json_path()
483482
if "uuid" not in data:
484483
raise ValueError("Invalid JSON: No UUID")
485484
c.stdout.write("[%s|%s] Installing %s\n" % (c.name, data["uuid"], path))

core/path.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# ----------------------------------------------------------------------
2+
# Path utilities
3+
# ----------------------------------------------------------------------
4+
# Copyright (C) 2007-2025 Gufo Labs
5+
# See LICENSE for details
6+
# ----------------------------------------------------------------------
7+
8+
# Python modules
9+
from pathlib import Path
10+
from typing import Optional
11+
import re
12+
13+
# NOC modules
14+
from .text import cyr_to_lat
15+
16+
_rx_safe_path = re.compile(r"[^a-z0-9\-\+]+", re.IGNORECASE)
17+
18+
19+
def _clean_component(s: str) -> str:
20+
"""
21+
Clean path component.
22+
23+
Args:
24+
s: Input string.
25+
26+
Returns:
27+
Cleaned output.
28+
"""
29+
r = cyr_to_lat(s)
30+
r = _rx_safe_path.sub(" ", r).strip()
31+
return r.replace(" ", "_")
32+
33+
34+
def safe_path(*args: str, sep: Optional[str] = None, suffix: Optional[str] = None) -> Path:
35+
"""
36+
Expand path components to safe relative path.
37+
38+
Suitable for collections. Unreadable or dangerous symbols are replaced
39+
with safe ones.
40+
41+
Examples:
42+
43+
``` python
44+
safe_path(name, suffix=".json")
45+
```
46+
47+
``` python
48+
safe_path(name, sep="|", suffix=".json")
49+
```
50+
51+
``` python
52+
safe_path(vendor, profile, suffix=".json")
53+
```
54+
55+
Args:
56+
args: Path components.
57+
sep: Separator, if set, every `args` element is split by `sep`.
58+
suffix: If set, append suffix to filename. Suffix doesn't include
59+
dot, by default, so i.e. `.json` form must be used, rather than
60+
`json`.
61+
62+
Returns:
63+
Formatted path component
64+
"""
65+
if sep:
66+
components = []
67+
for arg in args:
68+
components.extend(arg.split(sep))
69+
else:
70+
components = list(args)
71+
components = [_clean_component(x) for x in components]
72+
path = Path(*(x for x in components if x))
73+
if suffix:
74+
path = path.with_suffix(suffix)
75+
return path
76+
77+
78+
def safe_json_path(*args: str, sep: Optional[str] = "|", suffix: Optional[str] = ".json") -> Path:
79+
"""
80+
Expand path components to JSON file path.
81+
82+
Args:
83+
args: Path components.
84+
sep: Separator, if set, every `args` element is split by `sep`.
85+
suffix: If set, append suffix to filename. Suffix doesn't include
86+
dot, by default, so i.e. `.json` form must be used, rather than
87+
`json`.
88+
89+
Returns:
90+
Formatted path component
91+
"""
92+
return safe_path(*args, sep=sep, suffix=suffix)

core/protocols/__init__.py

Whitespace-only changes.

core/protocols/to_json.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# ----------------------------------------------------------------------
2+
# GetJsonPath protocol
3+
# ----------------------------------------------------------------------
4+
# Copyright (C) 2007-2025 Gufo Labs
5+
# See LICENSE for details
6+
# ----------------------------------------------------------------------
7+
8+
# Python modules
9+
from pathlib import Path
10+
from typing import Protocol
11+
12+
13+
class ToJson(Protocol):
14+
"""
15+
Saving to collections support.
16+
17+
Must implement methods:
18+
- get_json_path() - relative path in colelction
19+
- to_json() - collection content.
20+
"""
21+
22+
def get_json_path(self) -> Path: ...
23+
def to_json(self) -> str: ...

0 commit comments

Comments
 (0)