stoobly-agent 0.30.1__py3-none-any.whl → 0.30.7__py3-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.
Files changed (54) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/cli/config_cli.py +6 -4
  3. stoobly_agent/app/cli/helpers/handle_mock_service.py +1 -1
  4. stoobly_agent/app/proxy/handle_mock_service.py +6 -5
  5. stoobly_agent/app/proxy/handle_record_service.py +6 -7
  6. stoobly_agent/app/proxy/handle_replay_service.py +2 -2
  7. stoobly_agent/app/proxy/intercept_handler.py +6 -0
  8. stoobly_agent/app/proxy/mitmproxy/request_facade.py +9 -2
  9. stoobly_agent/app/proxy/utils/allowed_request_service.py +2 -2
  10. stoobly_agent/app/settings/types/proxy_settings.py +2 -1
  11. stoobly_agent/app/settings/url_rule.py +10 -4
  12. stoobly_agent/db/migrations/2022_03_17_060144_create_requests.py +1 -1
  13. stoobly_agent/db/migrations/2022_03_17_074916_create_responses.py +1 -1
  14. stoobly_agent/db/migrations/2022_05_10_003705_create_traces.py +1 -1
  15. stoobly_agent/db/migrations/2022_05_10_003840_create_trace_aliases.py +1 -1
  16. stoobly_agent/db/migrations/2022_06_29_234516_create_trace_requests.py +1 -1
  17. stoobly_agent/db/migrations/2022_06_29_235155_add_trace_request_reference_to_trace_aliases.py +1 -1
  18. stoobly_agent/db/migrations/2022_12_12_092437_align_requests.py +1 -1
  19. stoobly_agent/db/migrations/2022_12_28_092917_add_filter_columns_to_requests.py +1 -1
  20. stoobly_agent/db/migrations/2023_01_21_055426_create_scenarios.py +1 -1
  21. stoobly_agent/db/migrations/2023_01_21_060225_add_scenario_id_reference_to_requests.py +1 -1
  22. stoobly_agent/db/migrations/2023_02_02_022229_create_replayed_responses.py +1 -1
  23. stoobly_agent/db/migrations/2023_03_20_192909_add_http_version_column_to_requests.py +1 -1
  24. stoobly_agent/db/migrations/2023_03_20_220448_add_http_version_column_to_responses.py +1 -1
  25. stoobly_agent/db/migrations/2023_04_18_071327_align_requests.py +1 -1
  26. stoobly_agent/db/migrations/2023_05_15_212505_add_uuid_column_to_requests.py +1 -1
  27. stoobly_agent/db/migrations/2023_05_15_213119_add_uuid_column_to_scenarios.py +1 -1
  28. stoobly_agent/db/migrations/2023_05_29_053649_add_overwritable_column_to_scenarios.py +1 -1
  29. stoobly_agent/lib/orm/__init__.py +1 -1
  30. stoobly_agent/lib/orm/migrate_service.py +1 -1
  31. stoobly_agent/lib/orm/replayed_response.py +1 -1
  32. stoobly_agent/lib/orm/request.py +1 -1
  33. stoobly_agent/lib/orm/response.py +1 -1
  34. stoobly_agent/lib/orm/scenario.py +1 -1
  35. stoobly_agent/lib/orm/trace.py +1 -1
  36. stoobly_agent/lib/orm/trace_alias.py +1 -1
  37. stoobly_agent/lib/orm/trace_request.py +1 -1
  38. stoobly_agent/public/11-es2015.f88178b1234331d8bcb6.js +1 -0
  39. stoobly_agent/public/11-es5.f88178b1234331d8bcb6.js +1 -0
  40. stoobly_agent/public/{18-es2015.46d337c47cb41abec8ad.js → 18-es2015.ad1de4189b713d5a04ec.js} +1 -1
  41. stoobly_agent/public/{18-es5.46d337c47cb41abec8ad.js → 18-es5.ad1de4189b713d5a04ec.js} +1 -1
  42. stoobly_agent/public/dashboard.agent-alpha-0.30.5.tar.gz +0 -0
  43. stoobly_agent/public/index.html +1 -1
  44. stoobly_agent/public/{runtime-es2015.3a15c6ce90c8f8cce796.js → runtime-es2015.f2afb0be12804827d0a0.js} +1 -1
  45. stoobly_agent/public/{runtime-es5.3a15c6ce90c8f8cce796.js → runtime-es5.f2afb0be12804827d0a0.js} +1 -1
  46. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/METADATA +4 -4
  47. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/RECORD +51 -51
  48. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/WHEEL +1 -1
  49. stoobly_agent/public/11-es2015.25f04c94499eb7fe8383.js +0 -1
  50. stoobly_agent/public/11-es5.25f04c94499eb7fe8383.js +0 -1
  51. stoobly_agent/public/dashboard.agent-alpha-0.30.0.tar.gz +0 -0
  52. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/LICENSE +0 -0
  53. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/entry_points.txt +0 -0
  54. {stoobly_agent-0.30.1.dist-info → stoobly_agent-0.30.7.dist-info}/top_level.txt +0 -0
stoobly_agent/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '0.30.1'
2
+ VERSION = '0.30.7'
@@ -146,7 +146,7 @@ def rewrite(ctx):
146
146
  @rewrite.command(
147
147
  help="Set rewrite rule."
148
148
  )
149
- @click.option('--host', help='Request URL host.')
149
+ @click.option('--hostname', help='Request URL hostname.')
150
150
  @click.option(
151
151
  '--method',
152
152
  multiple=True,
@@ -169,6 +169,7 @@ def rewrite(ctx):
169
169
  type=click.Choice([request_component.BODY_PARAM, request_component.HEADER, request_component.QUERY_PARAM]),
170
170
  help='Request component type.'
171
171
  )
172
+ @click.option('--scheme', help='Request URL scheme.')
172
173
  @click.option('--value', help='Rewrite value.')
173
174
  def set(**kwargs):
174
175
  if kwargs['name'] or kwargs['value'] or kwargs['type']:
@@ -209,7 +210,7 @@ def set(**kwargs):
209
210
  settings.proxy.rewrite.set_rewrite_rules(project_key.id, rewrite_rules)
210
211
  else:
211
212
  parameter_rule_filter = lambda rule: rule.name == kwargs['name'] and rule.type == kwargs['type'] and rule.modes == modes
212
- url_rule_filter = lambda rule: rule.host == kwargs['host'] and rule.modes == modes
213
+ url_rule_filter = lambda rule: rule.modes == modes
213
214
 
214
215
  for rewrite_rule in filtered_rewrite_rules:
215
216
  # Parameter rules
@@ -485,13 +486,14 @@ def __select_parameter_rule(kwargs):
485
486
  }
486
487
 
487
488
  def __select_url_rule(kwargs):
488
- if kwargs['host'] == None or kwargs['port'] == None:
489
+ if kwargs['hostname'] == None or kwargs['port'] == None:
489
490
  return
490
491
 
491
492
  return {
492
- 'host': kwargs['host'],
493
+ 'hostname': kwargs['hostname'],
493
494
  'modes': list(kwargs['mode']),
494
495
  'port': kwargs['port'],
496
+ 'scheme': kwargs['scheme'],
495
497
  }
496
498
 
497
499
  def __project_key(settings):
@@ -11,4 +11,4 @@ def print_raw_response(response: requests.Response):
11
11
  facade = MitmproxyResponseFacade(mitmproxy_response)
12
12
  response_string = ResponseString(facade, None)
13
13
 
14
- print(response_string.get().decode())
14
+ print(response_string.get().decode(), end="")
@@ -32,10 +32,14 @@ class MockOptions(TypedDict):
32
32
  # @param settings [Dict]
33
33
  #
34
34
  def handle_request_mock_generic(context: MockContext, **options: MockOptions):
35
+ __mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
36
+
35
37
  intercept_settings = context.intercept_settings
36
38
  request: MitmproxyRequest = context.flow.request
37
39
  request_model = RequestModel(intercept_settings.settings)
38
40
 
41
+ policy = get_active_mode_policy(request, intercept_settings)
42
+
39
43
  rewrite_rules = intercept_settings.mock_rewrite_rules
40
44
  if len(rewrite_rules) > 0:
41
45
  # Rewrite request with paramter rules for mock
@@ -43,8 +47,6 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
43
47
  request_facade = MitmproxyRequestFacade(request)
44
48
  request_facade.with_parameter_rules(rewrite_rules).with_url_rules(rewrite_rules).rewrite()
45
49
 
46
- __mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
47
-
48
50
  # If ignore rules are set, then ignore specified request parameters
49
51
  ignore_rules = intercept_settings.ignore_rules
50
52
  if len(ignore_rules) > 0:
@@ -57,7 +59,6 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
57
59
  handle_failure = options['failure'] if 'failure' in options and callable(options['failure']) else None
58
60
 
59
61
  eval_request = inject_eval_request(request_model, intercept_settings)
60
- policy = get_active_mode_policy(request, intercept_settings)
61
62
 
62
63
  if policy == mock_policy.NONE:
63
64
  if handle_failure:
@@ -83,8 +84,8 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
83
84
  else:
84
85
  return bad_request(
85
86
  context.flow,
86
- "Valid env MOCK_POLICY: %s, %s, %s, Got: %s" %
87
- [mock_policy.ALL, mock_policy.FOUND, policy]
87
+ "Valid env MOCK_POLICY: %s, Got: %s" %
88
+ ([mock_policy.ALL, mock_policy.FOUND, mock_policy.NONE], policy)
88
89
  )
89
90
 
90
91
  __mock_hook(lifecycle_hooks.AFTER_MOCK, context)
@@ -23,18 +23,17 @@ LOG_ID = 'HandleRecord'
23
23
 
24
24
  def handle_response_record(context: RecordContext):
25
25
  flow = context.flow
26
- intercept_settings = context.intercept_settings
27
- request: MitmproxyRequest = flow.request
28
-
29
26
  disable_transfer_encoding(flow.response)
30
27
 
28
+ __record_hook(lifecycle_hooks.BEFORE_RECORD, context)
29
+
30
+ intercept_settings = context.intercept_settings
31
+ request: MitmproxyRequest = flow.request
31
32
  request_model = RequestModel(intercept_settings.settings)
32
33
 
33
34
  active_record_policy = get_active_mode_policy(request, intercept_settings)
34
35
  Logger.instance().debug(f"{LOG_ID}:RecordPolicy: {active_record_policy}")
35
36
 
36
- __record_hook(lifecycle_hooks.BEFORE_RECORD, context)
37
-
38
37
  if active_record_policy == record_policy.ALL:
39
38
  __record_request(context, request_model)
40
39
  elif active_record_policy == record_policy.FOUND:
@@ -55,8 +54,8 @@ def handle_response_record(context: RecordContext):
55
54
  if active_record_policy != record_policy.NONE:
56
55
  return bad_request(
57
56
  flow,
58
- "Valid env RECORD_POLICY: %s, %s, %s, Got: %s" %
59
- [record_policy.ALL, record_policy.FOUND, record_policy.NOT_FOUND, active_record_policy]
57
+ "Valid env RECORD_POLICY: %s, Got: %s" %
58
+ ([record_policy.ALL, record_policy.FOUND, record_policy.NOT_FOUND], active_record_policy)
60
59
  )
61
60
 
62
61
  def __record_handler(context: RecordContext, request_model: RequestModel):
@@ -12,6 +12,8 @@ from .utils.allowed_request_service import get_active_mode_policy
12
12
  LOG_ID = 'HandleReplay'
13
13
 
14
14
  def handle_request_replay(replay_context: ReplayContext):
15
+ __replay_hook(lifecycle_hooks.BEFORE_REPLAY, replay_context)
16
+
15
17
  request: MitmproxyRequest = replay_context.flow.request
16
18
  intercept_settings: InterceptSettings = replay_context.intercept_settings
17
19
 
@@ -34,8 +36,6 @@ def __replay_request(replay_context: ReplayContext):
34
36
  request_facade = MitmproxyRequestFacade(request)
35
37
  request_facade.with_parameter_rules(rewrite_rules).with_url_rules(rewrite_rules).rewrite()
36
38
 
37
- __replay_hook(lifecycle_hooks.BEFORE_REPLAY, replay_context)
38
-
39
39
  def __replay_hook(hook: str, replay_context: ReplayContext):
40
40
  intercept_settings: InterceptSettings = replay_context.intercept_settings
41
41
 
@@ -30,6 +30,9 @@ def request(flow: MitmproxyHTTPFlow):
30
30
 
31
31
  intercept_settings = InterceptSettings(Settings.instance(), request)
32
32
 
33
+ if not intercept_settings.active:
34
+ return
35
+
33
36
  __intercept_hook(lifecycle_hooks.BEFORE_REQUEST, flow, intercept_settings)
34
37
 
35
38
  active_mode = intercept_settings.mode
@@ -59,6 +62,9 @@ def response(flow: MitmproxyHTTPFlow):
59
62
  intercept_settings = InterceptSettings(Settings.instance(), request)
60
63
  intercept_settings.for_response()
61
64
 
65
+ if not intercept_settings.active:
66
+ return
67
+
62
68
  __intercept_hook(lifecycle_hooks.BEFORE_RESPONSE, flow, intercept_settings)
63
69
 
64
70
  active_mode = intercept_settings.mode
@@ -106,6 +106,10 @@ class MitmproxyRequestFacade(Request):
106
106
  def parameter_rules(self) -> List[ParameterRule]:
107
107
  return self.__parameter_rules
108
108
 
109
+ @property
110
+ def scheme(self):
111
+ return self.request.scheme
112
+
109
113
  def with_parameter_rules(self, rules: List[RewriteRule]):
110
114
  if type(rules) == list:
111
115
  self.__parameter_rules = self.select_parameter_rules(rules)
@@ -181,12 +185,15 @@ class MitmproxyRequestFacade(Request):
181
185
 
182
186
  def __rewrite_url(self, rewrites: List[UrlRule]):
183
187
  for rewrite in rewrites:
184
- if rewrite.host:
185
- self.request.host = rewrite.host
188
+ if rewrite.hostname:
189
+ self.request.host = rewrite.hostname
186
190
 
187
191
  if rewrite.port:
188
192
  self.request.port = int(rewrite.port)
189
193
 
194
+ if rewrite.scheme:
195
+ self.request.scheme = rewrite.scheme
196
+
190
197
  def __rewrite_headers(self, rewrites: List[ParameterRule]):
191
198
  self.__apply_headers(rewrites, self.__rewrite_handler)
192
199
 
@@ -13,7 +13,7 @@ def get_active_mode_policy(request: MitmproxyRequest, intercept_settings: Interc
13
13
  if intercept_settings.request_origin == request_origin.CLI:
14
14
  return intercept_settings.policy
15
15
 
16
- if intercept_settings.active and allowed_request(request, intercept_settings):
16
+ if allowed_request(request, intercept_settings):
17
17
  return intercept_settings.policy
18
18
  else:
19
19
  # If the request path does not match accepted paths, do not mock
@@ -27,7 +27,7 @@ def allowed_request(request: MitmproxyRequest, intercept_settings: InterceptSett
27
27
 
28
28
  # If an include rule(s) exists, then only requests matching these pattern(s) are allowed
29
29
  include_rules = intercept_settings.include_rules
30
- if not __request_included(request, include_rules) :
30
+ if not __request_included(request, include_rules):
31
31
  return False
32
32
 
33
33
  # If there are no exclude or include patterns, request is allowed
@@ -16,9 +16,10 @@ class ParameterRule(TypedDict):
16
16
  value: str
17
17
 
18
18
  class UrlRule(TypedDict):
19
- host: str
19
+ hostname: str
20
20
  modes: List[Mode]
21
21
  port: str
22
+ scheme: str
22
23
 
23
24
  class DataRules(TypedDict):
24
25
  mock_policy: MockPolicy
@@ -8,8 +8,8 @@ class UrlRule:
8
8
  self.update(url_rule)
9
9
 
10
10
  @property
11
- def host(self):
12
- return self.__host
11
+ def hostname(self):
12
+ return self.__hostname
13
13
 
14
14
  @property
15
15
  def modes(self):
@@ -19,15 +19,21 @@ class UrlRule:
19
19
  def port(self):
20
20
  return self.__port
21
21
 
22
+ @property
23
+ def scheme(self):
24
+ return self.__scheme
25
+
22
26
  def update(self, url_rule: IUrlRule):
23
27
  self.__url_rule = url_rule
24
- self.__host = self.__url_rule.get('host')
28
+ self.__hostname = self.__url_rule.get('hostname')
25
29
  self.__modes = self.__url_rule.get('modes')
26
30
  self.__port = self.__url_rule.get('port')
31
+ self.__scheme = self.__url_rule.get('scheme')
27
32
 
28
33
  def to_dict(self):
29
34
  return {
30
- 'host': self.__host,
35
+ 'hostname': self.__hostname,
31
36
  'modes': self.__modes,
32
37
  'port': self.__port,
38
+ 'scheme': self.__scheme,
33
39
  }
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class CreateRequests(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class CreateResponses(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
 
4
4
  class CreateTraces(Migration):
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class CreateTraceAliases(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class CreateTraceRequests(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class AddTraceRequestReferenceToTraceAliases(Migration):
4
4
 
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
  import requests
3
3
 
4
- from orator.migrations import Migration
4
+ from stoobly_orator.migrations import Migration
5
5
  from urllib.parse import urlparse
6
6
 
7
7
  from stoobly_agent.app.models.adapters.raw_http_request_adapter import RawHttpRequestAdapter
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class AddFilterColumnsToRequests(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class CreateScenarios(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  class AddScenarioIdReferenceToRequests(Migration):
4
4
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
 
4
4
  class CreateReplayedResponses(Migration):
@@ -1,6 +1,6 @@
1
1
  import pdb
2
2
 
3
- from orator.migrations import Migration
3
+ from stoobly_orator.migrations import Migration
4
4
 
5
5
  from stoobly_agent.lib.orm.request import Request
6
6
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
  from stoobly_agent.lib.orm.response import Response
4
4
 
@@ -1,7 +1,7 @@
1
1
  import requests
2
2
  import pdb
3
3
 
4
- from orator.migrations import Migration
4
+ from stoobly_orator.migrations import Migration
5
5
 
6
6
  from stoobly_agent.app.models.adapters.raw_http_response_adapter import RawHttpResponseAdapter
7
7
  from stoobly_agent.lib.orm.request import Request
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
  import uuid
3
3
 
4
- from orator.migrations import Migration
4
+ from stoobly_orator.migrations import Migration
5
5
 
6
6
  from stoobly_agent.app.proxy.record.request_string_control import RequestStringControl
7
7
  from stoobly_agent.lib.orm.request import Request
@@ -1,6 +1,6 @@
1
1
  import uuid
2
2
 
3
- from orator.migrations import Migration
3
+ from stoobly_orator.migrations import Migration
4
4
 
5
5
  from stoobly_agent.lib.orm.scenario import Scenario
6
6
 
@@ -1,4 +1,4 @@
1
- from orator.migrations import Migration
1
+ from stoobly_orator.migrations import Migration
2
2
 
3
3
 
4
4
  class AddOverwritableColumnToScenarios(Migration):
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import pdb
3
3
 
4
- from orator import DatabaseManager, Model
4
+ from stoobly_orator import DatabaseManager, Model
5
5
 
6
6
  from stoobly_agent.config.constants.env_vars import ENV
7
7
  from stoobly_agent.config.data_dir import DataDir
@@ -1,7 +1,7 @@
1
1
  import os
2
2
  import pdb
3
3
 
4
- from orator.migrations import Migrator, DatabaseMigrationRepository
4
+ from stoobly_orator.migrations import Migrator, DatabaseMigrationRepository
5
5
 
6
6
  from stoobly_agent.config.data_dir import DataDir
7
7
  from stoobly_agent.config.source_dir import SourceDir
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
  import requests
3
3
 
4
- from orator.orm import belongs_to
4
+ from stoobly_orator.orm import belongs_to
5
5
 
6
6
  from stoobly_agent.app.proxy.mitmproxy.response_facade import MitmproxyResponseFacade
7
7
  from stoobly_agent.app.proxy.record.response_string import ResponseString
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
  import uuid
3
3
 
4
- from orator.orm import belongs_to, has_many, has_one
4
+ from stoobly_orator.orm import belongs_to, has_many, has_one
5
5
 
6
6
  from stoobly_agent.lib.api.keys.project_key import LOCAL_PROJECT_ID
7
7
  from stoobly_agent.lib.api.keys.request_key import RequestKey
@@ -1,4 +1,4 @@
1
- from orator.orm import belongs_to
1
+ from stoobly_orator.orm import belongs_to
2
2
 
3
3
  from stoobly_agent.app.models.schemas.request import Request
4
4
 
@@ -1,7 +1,7 @@
1
1
  import pdb
2
2
  import uuid
3
3
 
4
- from orator.orm import has_many
4
+ from stoobly_orator.orm import has_many
5
5
 
6
6
  from stoobly_agent.lib import orm
7
7
  from stoobly_agent.lib.api.keys.project_key import LOCAL_PROJECT_ID
@@ -1,4 +1,4 @@
1
- from orator.orm import has_many
1
+ from stoobly_orator.orm import has_many
2
2
 
3
3
  from .base import Base
4
4
  from .trace_alias import TraceAlias
@@ -1,6 +1,6 @@
1
1
  import json
2
2
 
3
- from orator.orm import accessor, belongs_to
3
+ from stoobly_orator.orm import accessor, belongs_to
4
4
 
5
5
  from .base import Base
6
6
 
@@ -1,4 +1,4 @@
1
- from orator.orm import belongs_to, has_many
1
+ from stoobly_orator.orm import belongs_to, has_many
2
2
 
3
3
  from .base import Base
4
4
  from .trace_alias import TraceAlias