synapse 2.196.0__py311-none-any.whl → 2.198.0__py311-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of synapse might be problematic. Click here for more details.

Files changed (61) hide show
  1. synapse/axon.py +3 -0
  2. synapse/common.py +3 -0
  3. synapse/cortex.py +13 -11
  4. synapse/cryotank.py +2 -2
  5. synapse/lib/aha.py +3 -0
  6. synapse/lib/ast.py +277 -165
  7. synapse/lib/auth.py +39 -11
  8. synapse/lib/cell.py +24 -6
  9. synapse/lib/config.py +3 -3
  10. synapse/lib/hive.py +2 -1
  11. synapse/lib/hiveauth.py +10 -1
  12. synapse/lib/jsonstor.py +6 -5
  13. synapse/lib/layer.py +6 -5
  14. synapse/lib/multislabseqn.py +2 -2
  15. synapse/lib/node.py +10 -4
  16. synapse/lib/parser.py +46 -21
  17. synapse/lib/schemas.py +491 -1
  18. synapse/lib/snap.py +68 -26
  19. synapse/lib/storm.lark +13 -11
  20. synapse/lib/storm.py +13 -395
  21. synapse/lib/storm_format.py +3 -2
  22. synapse/lib/stormlib/graph.py +0 -61
  23. synapse/lib/stormlib/index.py +52 -0
  24. synapse/lib/stormtypes.py +16 -5
  25. synapse/lib/task.py +13 -2
  26. synapse/lib/urlhelp.py +1 -1
  27. synapse/lib/version.py +2 -2
  28. synapse/models/doc.py +62 -0
  29. synapse/models/infotech.py +18 -0
  30. synapse/models/orgs.py +6 -4
  31. synapse/models/risk.py +9 -0
  32. synapse/models/syn.py +18 -2
  33. synapse/tests/files/stormpkg/badendpoints.yaml +7 -0
  34. synapse/tests/files/stormpkg/testpkg.yaml +8 -0
  35. synapse/tests/test_cortex.py +108 -0
  36. synapse/tests/test_datamodel.py +7 -0
  37. synapse/tests/test_lib_aha.py +12 -42
  38. synapse/tests/test_lib_ast.py +57 -0
  39. synapse/tests/test_lib_auth.py +143 -2
  40. synapse/tests/test_lib_boss.py +15 -6
  41. synapse/tests/test_lib_cell.py +43 -0
  42. synapse/tests/test_lib_grammar.py +54 -2
  43. synapse/tests/test_lib_lmdbslab.py +24 -0
  44. synapse/tests/test_lib_storm.py +20 -0
  45. synapse/tests/test_lib_stormlib_index.py +39 -0
  46. synapse/tests/test_lib_stormlib_macro.py +3 -3
  47. synapse/tests/test_lib_stormtypes.py +14 -2
  48. synapse/tests/test_lib_task.py +31 -13
  49. synapse/tests/test_model_doc.py +38 -0
  50. synapse/tests/test_model_infotech.py +13 -0
  51. synapse/tests/test_model_orgs.py +7 -0
  52. synapse/tests/test_model_risk.py +6 -0
  53. synapse/tests/test_model_syn.py +58 -0
  54. synapse/tests/test_tools_genpkg.py +10 -0
  55. synapse/tools/genpkg.py +2 -2
  56. synapse/tools/hive/load.py +1 -0
  57. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/METADATA +1 -1
  58. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/RECORD +61 -58
  59. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/LICENSE +0 -0
  60. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/WHEEL +0 -0
  61. {synapse-2.196.0.dist-info → synapse-2.198.0.dist-info}/top_level.txt +0 -0
synapse/lib/schemas.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import synapse.lib.const as s_const
2
2
  import synapse.lib.config as s_config
3
+ import synapse.lib.grammar as s_grammar
3
4
  import synapse.lib.msgpack as s_msgpack
4
-
5
+ import synapse.lib.version as s_version
5
6
 
6
7
  easyPermSchema = {
7
8
  'type': 'object',
@@ -551,3 +552,492 @@ stixIngestBundleSchema = {
551
552
  },
552
553
  }
553
554
  reqValidStixIngestBundle = s_config.getJsValidator(stixIngestBundleSchema)
555
+
556
+ _reqValidGdefSchema = {
557
+ 'type': 'object',
558
+ 'properties': {
559
+ 'iden': {'type': 'string', 'pattern': s_config.re_iden},
560
+ 'name': {'type': 'string', 'minLength': 1},
561
+ 'desc': {'type': 'string', 'default': ''},
562
+ 'scope': {'type': 'string', 'enum': ['user', 'power-up']},
563
+ 'creator': {'type': 'string', 'pattern': s_config.re_iden},
564
+ 'power-up': {'type': 'string', 'minLength': 1},
565
+ 'maxsize': {'type': 'number', 'minimum': 0},
566
+ 'existing': {'type': 'array', 'items': {'type': 'string'}},
567
+ 'created': {'type': 'number'},
568
+ 'updated': {'type': 'number'},
569
+ 'refs': {'type': 'boolean', 'default': False},
570
+ 'edges': {'type': 'boolean', 'default': True},
571
+ 'edgelimit': {'type': 'number', 'default': 3000},
572
+ 'degrees': {'type': ['integer', 'null'], 'minimum': 0},
573
+ 'filterinput': {'type': 'boolean', 'default': True},
574
+ 'yieldfiltered': {'type': 'boolean', 'default': False},
575
+ 'filters': {
576
+ 'type': ['array', 'null'],
577
+ 'items': {'type': 'string'}
578
+ },
579
+ 'pivots': {
580
+ 'type': ['array', 'null'],
581
+ 'items': {'type': 'string'}
582
+ },
583
+ 'forms': {
584
+ 'type': 'object',
585
+ 'patternProperties': {
586
+ '^.*$': {
587
+ 'type': 'object',
588
+ 'properties': {
589
+ 'filters': {
590
+ 'type': ['array', 'null'],
591
+ 'items': {'type': 'string'}
592
+ },
593
+ 'pivots': {
594
+ 'type': ['array', 'null'],
595
+ 'items': {'type': 'string'}
596
+ }
597
+ },
598
+ 'additionalProperties': False,
599
+ }
600
+ }
601
+ },
602
+ 'permissions': s_msgpack.deepcopy(easyPermSchema)
603
+ },
604
+ 'additionalProperties': False,
605
+ 'required': ['iden', 'name', 'scope'],
606
+ 'allOf': [
607
+ {
608
+ 'if': {'properties': {'scope': {'const': 'power-up'}}},
609
+ 'then': {'required': ['power-up']},
610
+ 'else': {'required': ['creator']},
611
+ }
612
+ ]
613
+ }
614
+ reqValidGdef = s_config.getJsValidator(_reqValidGdefSchema)
615
+
616
+ _reqValidPermDefSchema = {
617
+ 'type': 'object',
618
+ 'properties': {
619
+ 'perm': {'type': 'array', 'items': {'type': 'string'}},
620
+ 'desc': {'type': 'string'},
621
+ 'gate': {'type': 'string'},
622
+ 'ex': {'type': 'string'}, # Example string
623
+ 'workflowconfig': {'type': 'boolean'},
624
+ 'default': {'type': 'boolean', 'default': False},
625
+ },
626
+ 'required': ['perm', 'desc', 'gate'],
627
+ }
628
+
629
+ reqValidPermDef = s_config.getJsValidator(_reqValidPermDefSchema)
630
+
631
+ # N.B. This is kept in sync with s_datamodel.Datamodel().types
632
+ # with the DatamodelTest.test_datamodel_schema_basetypes test.
633
+ datamodel_basetypes = [
634
+ 'int',
635
+ 'float',
636
+ 'range',
637
+ 'str',
638
+ 'hex',
639
+ 'bool',
640
+ 'time',
641
+ 'duration',
642
+ 'ival',
643
+ 'guid',
644
+ 'syn:tag:part',
645
+ 'syn:tag',
646
+ 'comp',
647
+ 'loc',
648
+ 'ndef',
649
+ 'array',
650
+ 'edge',
651
+ 'timeedge',
652
+ 'data',
653
+ 'nodeprop',
654
+ 'hugenum',
655
+ 'taxon',
656
+ 'taxonomy',
657
+ 'velocity',
658
+ ]
659
+
660
+ _reqValidPkgdefSchema = {
661
+ 'type': 'object',
662
+ 'properties': {
663
+ 'name': {'type': 'string'},
664
+ 'version': {
665
+ 'type': 'string',
666
+ 'pattern': s_version.semverstr,
667
+ },
668
+ 'build': {
669
+ 'type' 'object'
670
+ 'properties': {
671
+ 'time': {'type': 'number'},
672
+ },
673
+ 'required': ['time'],
674
+ },
675
+ 'codesign': {
676
+ 'type': 'object',
677
+ 'properties': {
678
+ 'sign': {'type': 'string'},
679
+ 'cert': {'type': 'string'},
680
+ },
681
+ 'required': ['cert', 'sign'],
682
+ },
683
+ # TODO: Remove me after Synapse 3.0.0.
684
+ 'synapse_minversion': {
685
+ 'type': ['array', 'null'],
686
+ 'items': {'type': 'number'}
687
+ },
688
+ 'synapse_version': {
689
+ 'type': 'string',
690
+ },
691
+ 'modules': {
692
+ 'type': ['array', 'null'],
693
+ 'items': {'$ref': '#/definitions/module'}
694
+ },
695
+ 'docs': {
696
+ 'type': ['array', 'null'],
697
+ 'items': {'$ref': '#/definitions/doc'},
698
+ },
699
+ 'logo': {
700
+ 'type': 'object',
701
+ 'properties': {
702
+ 'mime': {'type': 'string'},
703
+ 'file': {'type': 'string'},
704
+ },
705
+ 'additionalProperties': True,
706
+ 'required': ['mime', 'file'],
707
+ },
708
+ 'commands': {
709
+ 'type': ['array', 'null'],
710
+ 'items': {'$ref': '#/definitions/command'},
711
+ },
712
+ 'graphs': {
713
+ 'type': ['array', 'null'],
714
+ 'items': s_msgpack.deepcopy(_reqValidGdefSchema, use_list=True),
715
+ },
716
+ 'desc': {'type': 'string'},
717
+ 'svciden': {'type': ['string', 'null'], 'pattern': s_config.re_iden},
718
+ 'onload': {'type': 'string'},
719
+ 'author': {
720
+ 'type': 'object',
721
+ 'properties': {
722
+ 'url': {'type': 'string'},
723
+ 'name': {'type': 'string'},
724
+ },
725
+ 'required': ['name', 'url'],
726
+ },
727
+ 'depends': {
728
+ 'properties': {
729
+ 'requires': {'type': 'array', 'items': {'$ref': '#/definitions/require'}},
730
+ 'conflicts': {'type': 'array', 'items': {'$ref': '#/definitions/conflict'}},
731
+ },
732
+ 'additionalProperties': True,
733
+ },
734
+ 'perms': {
735
+ 'type': 'array',
736
+ 'items': s_msgpack.deepcopy(_reqValidPermDefSchema),
737
+ },
738
+ 'configvars': {
739
+ 'type': 'array',
740
+ 'items': {
741
+ 'type': 'object',
742
+ 'properties': {
743
+ 'name': {'type': 'string'},
744
+ 'varname': {'type': 'string'},
745
+ 'desc': {'type': 'string'},
746
+ 'default': {},
747
+ 'workflowconfig': {'type': 'boolean'},
748
+ 'type': {'$ref': '#/definitions/configvartype'},
749
+ 'scopes': {
750
+ 'type': 'array',
751
+ 'items': {
752
+ 'type': 'string',
753
+ 'enum': ['global', 'self']
754
+ },
755
+ },
756
+ },
757
+ 'required': ['name', 'varname', 'desc', 'type', 'scopes'],
758
+ },
759
+ },
760
+ },
761
+ 'additionalProperties': True,
762
+ 'required': ['name', 'version'],
763
+ 'definitions': {
764
+ 'doc': {
765
+ 'type': 'object',
766
+ 'properties': {
767
+ 'title': {'type': 'string'},
768
+ 'content': {'type': 'string'},
769
+ },
770
+ 'additionalProperties': True,
771
+ 'required': ['title', 'content'],
772
+ },
773
+ 'module': {
774
+ 'type': 'object',
775
+ 'properties': {
776
+ 'name': {'type': 'string'},
777
+ 'storm': {'type': 'string'},
778
+ 'modconf': {'type': 'object'},
779
+ 'apidefs': {
780
+ 'type': ['array', 'null'],
781
+ 'items': {'$ref': '#/definitions/apidef'},
782
+ },
783
+ 'asroot': {'type': 'boolean'},
784
+ 'asroot:perms': {'type': 'array',
785
+ 'items': {'type': 'array',
786
+ 'items': {'type': 'string'}},
787
+ },
788
+ },
789
+ 'additionalProperties': True,
790
+ 'required': ['name', 'storm']
791
+ },
792
+ 'apidef': {
793
+ 'type': 'object',
794
+ 'properties': {
795
+ 'name': {'type': 'string'},
796
+ 'desc': {'type': 'string'},
797
+ 'deprecated': {'$ref': '#/definitions/deprecatedItem'},
798
+ 'type': {
799
+ 'type': 'object',
800
+ 'properties': {
801
+ 'type': {
802
+ 'type': 'string',
803
+ 'enum': ['function']
804
+ },
805
+ 'args': {
806
+ 'type': 'array',
807
+ 'items': {'$ref': '#/definitions/apiarg'},
808
+ },
809
+ 'returns': {
810
+ 'type': 'object',
811
+ 'properties': {
812
+ 'name': {
813
+ 'type': 'string',
814
+ 'enum': ['yields'],
815
+ },
816
+ 'desc': {'type': 'string'},
817
+ 'type': {
818
+ 'oneOf': [
819
+ {'$ref': '#/definitions/apitype'},
820
+ {'type': 'array', 'items': {'$ref': '#/definitions/apitype'}},
821
+ ],
822
+ },
823
+ },
824
+ 'additionalProperties': False,
825
+ 'required': ['type', 'desc']
826
+ },
827
+ },
828
+ 'additionalProperties': False,
829
+ 'required': ['type', 'returns'],
830
+ },
831
+ },
832
+ 'additionalProperties': False,
833
+ 'required': ['name', 'desc', 'type']
834
+ },
835
+ 'apiarg': {
836
+ 'type': 'object',
837
+ 'properties': {
838
+ 'name': {'type': 'string'},
839
+ 'desc': {'type': 'string'},
840
+ 'type': {
841
+ 'oneOf': [
842
+ {'$ref': '#/definitions/apitype'},
843
+ {'type': 'array', 'items': {'$ref': '#/definitions/apitype'}},
844
+ ],
845
+ },
846
+ 'default': {'type': ['boolean', 'integer', 'string', 'null']},
847
+ },
848
+ 'additionalProperties': False,
849
+ 'required': ['name', 'desc', 'type']
850
+ },
851
+ 'deprecatedItem': {
852
+ 'type': 'object',
853
+ 'properties': {
854
+ 'eolvers': {'type': 'string', 'minLength': 1,
855
+ 'description': "The version which will not longer support the item."},
856
+ 'eoldate': {'type': 'string', 'minLength': 1,
857
+ 'description': 'Optional string indicating Synapse releases after this date may no longer support the item.'},
858
+ 'mesg': {'type': ['string', 'null'], 'default': None,
859
+ 'description': 'Optional message to include in the warning text.'}
860
+ },
861
+ 'oneOf': [
862
+ {
863
+ 'required': ['eolvers'],
864
+ 'not': {'required': ['eoldate']}
865
+ },
866
+ {
867
+ 'required': ['eoldate'],
868
+ 'not': {'required': ['eolvers']}
869
+ }
870
+ ],
871
+ 'additionalProperties': False,
872
+ },
873
+ 'apitype': {
874
+ 'type': 'string',
875
+ },
876
+ 'command': {
877
+ 'type': 'object',
878
+ 'properties': {
879
+ 'name': {
880
+ 'type': 'string',
881
+ 'pattern': s_grammar.re_scmd
882
+ },
883
+ 'endpoints': {
884
+ 'type': 'array',
885
+ 'items': {
886
+ 'type': 'object',
887
+ 'properties': {
888
+ 'path': {'type': 'string'},
889
+ 'host': {'type': 'string'},
890
+ 'desc': {'type': 'string'},
891
+ },
892
+ 'required': ['path'],
893
+ 'additionalProperties': False
894
+ }
895
+ },
896
+ 'cmdargs': {
897
+ 'type': ['array', 'null'],
898
+ 'items': {'$ref': '#/definitions/cmdarg'},
899
+ },
900
+ 'cmdinputs': {
901
+ 'type': ['array', 'null'],
902
+ 'items': {'$ref': '#/definitions/cmdinput'},
903
+ },
904
+ 'storm': {'type': 'string'},
905
+ 'forms': {'$ref': '#/definitions/cmdformhints'},
906
+ 'perms': {'type': 'array',
907
+ 'items': {'type': 'array',
908
+ 'items': {'type': 'string'}},
909
+ },
910
+ },
911
+ 'additionalProperties': True,
912
+ 'required': ['name', 'storm']
913
+ },
914
+ 'cmdarg': {
915
+ 'type': 'array',
916
+ 'items': [
917
+ {'type': 'string'},
918
+ {
919
+ 'type': 'object',
920
+ 'properties': {
921
+ 'help': {'type': 'string'},
922
+ 'default': {},
923
+ 'dest': {'type': 'string'},
924
+ 'required': {'type': 'boolean'},
925
+ 'action': {'type': 'string'},
926
+ 'nargs': {'type': ['string', 'integer']},
927
+ 'choices': {
928
+ 'type': 'array',
929
+ 'uniqueItems': True,
930
+ 'minItems': 1,
931
+ },
932
+ 'type': {
933
+ 'type': 'string',
934
+ 'enum': s_msgpack.deepcopy(datamodel_basetypes),
935
+ },
936
+ },
937
+ }
938
+ ],
939
+ 'additionalItems': False,
940
+ },
941
+ 'cmdinput': {
942
+ 'type': 'object',
943
+ 'properties': {
944
+ 'form': {'type': 'string'},
945
+ 'help': {'type': 'string'},
946
+ },
947
+ 'additionalProperties': True,
948
+ 'required': ['form'],
949
+ },
950
+ 'configvartype': {
951
+ 'anyOf': [
952
+ {'type': 'array', 'items': {'$ref': '#/definitions/configvartype'}},
953
+ {'type': 'string'},
954
+ ]
955
+ },
956
+ # deprecated
957
+ 'cmdformhints': {
958
+ 'type': 'object',
959
+ 'properties': {
960
+ 'input': {
961
+ 'type': 'array',
962
+ 'uniqueItems': True,
963
+ 'items': {
964
+ 'type': 'string',
965
+ }
966
+ },
967
+ 'output': {
968
+ 'type': 'array',
969
+ 'uniqueItems': True,
970
+ 'items': {
971
+ 'type': 'string',
972
+ }
973
+ },
974
+ 'nodedata': {
975
+ 'type': 'array',
976
+ 'uniqueItems': True,
977
+ 'items': {
978
+ 'type': 'array',
979
+ 'items': [
980
+ {'type': 'string'},
981
+ {'type': 'string'},
982
+ ],
983
+ 'additionalItems': False,
984
+ },
985
+ },
986
+ }
987
+ },
988
+ 'require': {
989
+ 'type': 'object',
990
+ 'properties': {
991
+ 'name': {'type': 'string'},
992
+ 'version': {'type': 'string'},
993
+ 'desc': {'type': 'string'},
994
+ 'optional': {'type': 'boolean'},
995
+ },
996
+ 'additionalItems': True,
997
+ 'required': ('name', 'version'),
998
+ },
999
+ 'conflict': {
1000
+ 'type': 'object',
1001
+ 'properties': {
1002
+ 'name': {'type': 'string'},
1003
+ 'version': {'type': 'string'},
1004
+ 'desc': {'type': 'string'},
1005
+ },
1006
+ 'additionalItems': True,
1007
+ 'required': ('name',),
1008
+ },
1009
+ }
1010
+ }
1011
+ reqValidPkgdef = s_config.getJsValidator(_reqValidPkgdefSchema)
1012
+
1013
+ _reqValidDdefSchema = {
1014
+ 'type': 'object',
1015
+ 'properties': {
1016
+ 'name': {'type': 'string'},
1017
+ 'storm': {'type': 'string'},
1018
+ 'view': {'type': 'string', 'pattern': s_config.re_iden},
1019
+ 'user': {'type': 'string', 'pattern': s_config.re_iden},
1020
+ 'iden': {'type': 'string', 'pattern': s_config.re_iden},
1021
+ 'enabled': {'type': 'boolean', 'default': True},
1022
+ 'stormopts': {
1023
+ 'oneOf': [
1024
+ {'type': 'null'},
1025
+ {'$ref': '#/definitions/stormopts'}
1026
+ ]
1027
+ }
1028
+ },
1029
+ 'additionalProperties': True,
1030
+ 'required': ['iden', 'user', 'storm'],
1031
+ 'definitions': {
1032
+ 'stormopts': {
1033
+ 'type': 'object',
1034
+ 'properties': {
1035
+ 'repr': {'type': 'boolean'},
1036
+ 'path': {'type': 'string'},
1037
+ 'show': {'type': 'array', 'items': {'type': 'string'}}
1038
+ },
1039
+ 'additionalProperties': True,
1040
+ },
1041
+ }
1042
+ }
1043
+ reqValidDdef = s_config.getJsValidator(_reqValidDdefSchema)
synapse/lib/snap.py CHANGED
@@ -138,6 +138,12 @@ class ProtoNode:
138
138
 
139
139
  if not await self.ctx.snap.hasNodeEdge(self.buid, verb, s_common.uhex(n2iden)):
140
140
  self.edges.add(tupl)
141
+
142
+ if len(self.edges) >= 1000:
143
+ nodeedits = self.ctx.getNodeEdits()
144
+ await self.ctx.snap.applyNodeEdits(nodeedits)
145
+ self.edges.clear()
146
+
141
147
  return True
142
148
 
143
149
  return False
@@ -169,6 +175,12 @@ class ProtoNode:
169
175
 
170
176
  if await self.ctx.snap.layers[-1].hasNodeEdge(self.buid, verb, s_common.uhex(n2iden)):
171
177
  self.edgedels.add(tupl)
178
+
179
+ if len(self.edgedels) >= 1000:
180
+ nodeedits = self.ctx.getNodeEdits()
181
+ await self.ctx.snap.applyNodeEdits(nodeedits)
182
+ self.edgedels.clear()
183
+
172
184
  return True
173
185
 
174
186
  return False
@@ -1062,6 +1074,23 @@ class Snap(s_base.Base):
1062
1074
  mesg = f'No property named "{full}".'
1063
1075
  raise s_exc.NoSuchProp(mesg=mesg)
1064
1076
 
1077
+ if isinstance(valu, dict) and isinstance(prop.type, s_types.Guid) and cmpr == '=':
1078
+ if prop.isform:
1079
+ if (node := await self._getGuidNodeByDict(prop, valu)) is not None:
1080
+ yield node
1081
+ return
1082
+
1083
+ fname = prop.type.name
1084
+ if (form := prop.modl.form(fname)) is None:
1085
+ mesg = f'The property "{full}" type "{fname}" is not a form and cannot be lifted using a dictionary.'
1086
+ raise s_exc.BadTypeValu(mesg=mesg)
1087
+
1088
+ if (node := await self._getGuidNodeByDict(form, valu)) is None:
1089
+ return
1090
+
1091
+ norm = False
1092
+ valu = node.ndef[1]
1093
+
1065
1094
  if norm:
1066
1095
  cmprvals = prop.type.getStorCmprs(cmpr, valu)
1067
1096
  # an empty return probably means ?= with invalid value
@@ -1398,10 +1427,6 @@ class Snap(s_base.Base):
1398
1427
 
1399
1428
  async def _addGuidNodeByDict(self, form, vals, props=None):
1400
1429
 
1401
- norms = {}
1402
- counts = []
1403
- proplist = []
1404
-
1405
1430
  if props is None:
1406
1431
  props = {}
1407
1432
 
@@ -1439,7 +1464,32 @@ class Snap(s_base.Base):
1439
1464
  raise e
1440
1465
  await self.warn(f'Skipping bad value for prop {form.name}:{name}: {mesg}')
1441
1466
 
1442
- for name, valu in vals.items():
1467
+ norms, proplist = self._normGuidNodeDict(form, vals)
1468
+
1469
+ iden = s_common.guid(proplist)
1470
+ node = await self._getGuidNodeByNorms(form, iden, norms)
1471
+
1472
+ async with self.getEditor() as editor:
1473
+
1474
+ if node is not None:
1475
+ proto = editor.loadNode(node)
1476
+ else:
1477
+ proto = await editor.addNode(form.name, iden)
1478
+ for name, (prop, valu, info) in norms.items():
1479
+ await proto.set(name, valu, norminfo=info)
1480
+
1481
+ # ensure the non-deconf props are set
1482
+ for name, (valu, info) in props.items():
1483
+ await proto.set(name, valu, norminfo=info)
1484
+
1485
+ return await self.getNodeByBuid(proto.buid)
1486
+
1487
+ def _normGuidNodeDict(self, form, props):
1488
+
1489
+ norms = {}
1490
+ proplist = []
1491
+
1492
+ for name, valu in props.items():
1443
1493
 
1444
1494
  try:
1445
1495
  prop = form.reqProp(name)
@@ -1458,22 +1508,24 @@ class Snap(s_base.Base):
1458
1508
 
1459
1509
  proplist.sort()
1460
1510
 
1511
+ return norms, proplist
1512
+
1513
+ async def _getGuidNodeByDict(self, form, props):
1514
+ norms, proplist = self._normGuidNodeDict(form, props)
1515
+ return await self._getGuidNodeByNorms(form, s_common.guid(proplist), norms)
1516
+
1517
+ async def _getGuidNodeByNorms(self, form, iden, norms):
1518
+
1461
1519
  # check first for an exact match via our same deconf strategy
1462
- iden = s_common.guid(proplist)
1463
1520
 
1464
1521
  node = await self.getNodeByNdef((form.full, iden))
1465
1522
  if node is not None:
1466
1523
 
1467
1524
  # ensure we still match the property deconf criteria
1468
- for name, (prop, norm, info) in norms.items():
1525
+ for (prop, norm, info) in norms.values():
1469
1526
  if not self._filtByPropAlts(node, prop, norm):
1470
1527
  break
1471
1528
  else:
1472
- # ensure the non-deconf props are set
1473
- async with self.getEditor() as editor:
1474
- proto = editor.loadNode(node)
1475
- for name, (valu, info) in props.items():
1476
- await proto.set(name, valu, norminfo=info)
1477
1529
  return node
1478
1530
 
1479
1531
  # TODO there is an opportunity here to populate
@@ -1482,8 +1534,10 @@ class Snap(s_base.Base):
1482
1534
  # if we lookup a node and it no longer passes the
1483
1535
  # filter...
1484
1536
 
1537
+ counts = []
1538
+
1485
1539
  # no exact match. lets do some counting.
1486
- for name, (prop, norm, info) in norms.items():
1540
+ for (prop, norm, info) in norms.values():
1487
1541
  count = await self._getPropAltCount(prop, norm)
1488
1542
  counts.append((count, prop, norm))
1489
1543
 
@@ -1499,21 +1553,9 @@ class Snap(s_base.Base):
1499
1553
  if not self._filtByPropAlts(node, prop, norm):
1500
1554
  break
1501
1555
  else:
1502
- # ensure the non-deconf props are set
1503
- async with self.getEditor() as editor:
1504
- proto = editor.loadNode(node)
1505
- for name, (valu, info) in props.items():
1506
- await proto.set(name, valu, norminfo=info)
1507
1556
  return node
1508
1557
 
1509
- async with self.getEditor() as editor:
1510
- proto = await editor.addNode(form.name, iden)
1511
- for name, (prop, valu, info) in norms.items():
1512
- await proto.set(name, valu, norminfo=info)
1513
- for name, (valu, info) in props.items():
1514
- await proto.set(name, valu, norminfo=info)
1515
-
1516
- return await self.getNodeByBuid(proto.buid)
1558
+ return None
1517
1559
 
1518
1560
  async def _getPropAltCount(self, prop, valu):
1519
1561
  count = 0