piccolo 1.24.1__py3-none-any.whl → 1.25.0__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.
piccolo/__init__.py CHANGED
@@ -1 +1 @@
1
- __VERSION__ = "1.24.1"
1
+ __VERSION__ = "1.25.0"
@@ -2,12 +2,16 @@ from __future__ import annotations
2
2
 
3
3
  import importlib
4
4
  import os
5
+ import pathlib
6
+ import string
5
7
  import sys
6
8
  import typing as t
7
9
 
8
10
  import black
9
11
  import jinja2
10
12
 
13
+ from piccolo.conf.apps import PiccoloConfUpdater
14
+
11
15
  TEMPLATE_DIRECTORY = os.path.join(
12
16
  os.path.dirname(os.path.abspath(__file__)), "templates"
13
17
  )
@@ -30,13 +34,36 @@ def module_exists(module_name: str) -> bool:
30
34
  return True
31
35
 
32
36
 
33
- def new_app(app_name: str, root: str = "."):
34
- print(f"Creating {app_name} app ...")
37
+ APP_NAME_ALLOWED_CHARACTERS = [*string.ascii_lowercase, *string.digits, "_"]
35
38
 
36
- app_root = os.path.join(root, app_name)
37
39
 
38
- if os.path.exists(app_root):
39
- sys.exit("Folder already exists - exiting.")
40
+ def validate_app_name(app_name: str):
41
+ """
42
+ Make sure the app name is something which is a valid Python package name.
43
+
44
+ :raises ValueError:
45
+ If ``app_name`` isn't valid.
46
+
47
+ """
48
+ for char in app_name:
49
+ if not char.lower() in APP_NAME_ALLOWED_CHARACTERS:
50
+ raise ValueError(
51
+ f"The app name contains a disallowed character: `{char}`. "
52
+ "It must only include a-z, 0-9, and _ characters."
53
+ )
54
+
55
+
56
+ def get_app_module(app_name: str, root: str) -> str:
57
+ return ".".join([*pathlib.Path(root).parts, app_name, "piccolo_app"])
58
+
59
+
60
+ def new_app(app_name: str, root: str = ".", register: bool = False):
61
+ print(f"Creating {app_name} app ...")
62
+
63
+ try:
64
+ validate_app_name(app_name=app_name)
65
+ except ValueError as exception:
66
+ sys.exit(str(exception))
40
67
 
41
68
  if module_exists(app_name):
42
69
  sys.exit(
@@ -44,7 +71,12 @@ def new_app(app_name: str, root: str = "."):
44
71
  "Python module. Please choose a different name for your app."
45
72
  )
46
73
 
47
- os.mkdir(app_root)
74
+ app_root = os.path.join(root, app_name)
75
+
76
+ if os.path.exists(app_root):
77
+ sys.exit("Folder already exists - exiting.")
78
+
79
+ os.makedirs(app_root)
48
80
 
49
81
  with open(os.path.join(app_root, "__init__.py"), "w"):
50
82
  pass
@@ -69,16 +101,22 @@ def new_app(app_name: str, root: str = "."):
69
101
  with open(os.path.join(migrations_folder_path, "__init__.py"), "w"):
70
102
  pass
71
103
 
104
+ if register:
105
+ app_module = get_app_module(app_name=app_name, root=root)
106
+ PiccoloConfUpdater().register_app(app_module=app_module)
107
+
72
108
 
73
- def new(app_name: str, root: str = "."):
109
+ def new(app_name: str, root: str = ".", register: bool = False):
74
110
  """
75
111
  Creates a new Piccolo app.
76
112
 
77
113
  :param app_name:
78
114
  The name of the new app.
79
115
  :param root:
80
- Where to create the app e.g. /my/folder. By default it creates the
116
+ Where to create the app e.g. ./my/folder. By default it creates the
81
117
  app in the current directory.
118
+ :param register:
119
+ If True, the app is registered automatically in piccolo_conf.py.
82
120
 
83
121
  """
84
- new_app(app_name=app_name, root=root)
122
+ new_app(app_name=app_name, root=root, register=register)
@@ -5,7 +5,7 @@ the APP_CONFIG.
5
5
 
6
6
  import os
7
7
 
8
- from piccolo.conf.apps import AppConfig, table_finder
8
+ from piccolo.conf.apps import AppConfig, table_finder, get_package
9
9
 
10
10
 
11
11
  CURRENT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
@@ -18,7 +18,8 @@ APP_CONFIG = AppConfig(
18
18
  'piccolo_migrations'
19
19
  ),
20
20
  table_classes=table_finder(
21
- modules=["{{ app_name }}.tables"],
21
+ modules=[".tables"],
22
+ package=get_package(__name__),
22
23
  exclude_imported=True
23
24
  ),
24
25
  migration_dependencies=[],
@@ -12,7 +12,7 @@ from esmerald import (
12
12
  post,
13
13
  put,
14
14
  )
15
- from esmerald.config import StaticFilesConfig
15
+ from esmerald.core.config import StaticFilesConfig
16
16
  from piccolo.engine import engine_finder
17
17
  from piccolo.utils.pydantic import create_pydantic_model
18
18
  from piccolo_admin.endpoints import create_admin
@@ -45,11 +45,11 @@ class Combination(CombinableMixin):
45
45
  )
46
46
 
47
47
  @property
48
- def querystring_for_update(self) -> QueryString:
48
+ def querystring_for_update_and_delete(self) -> QueryString:
49
49
  return QueryString(
50
50
  "({} " + self.operator + " {})",
51
- self.first.querystring_for_update,
52
- self.second.querystring_for_update,
51
+ self.first.querystring_for_update_and_delete,
52
+ self.second.querystring_for_update_and_delete,
53
53
  )
54
54
 
55
55
  def __str__(self):
@@ -131,7 +131,7 @@ class WhereRaw(CombinableMixin):
131
131
  self.querystring = QueryString(sql, *args)
132
132
 
133
133
  @property
134
- def querystring_for_update(self) -> QueryString:
134
+ def querystring_for_update_and_delete(self) -> QueryString:
135
135
  return self.querystring
136
136
 
137
137
  def __str__(self):
@@ -218,7 +218,7 @@ class Where(CombinableMixin):
218
218
  return QueryString(template, *args)
219
219
 
220
220
  @property
221
- def querystring_for_update(self) -> QueryString:
221
+ def querystring_for_update_and_delete(self) -> QueryString:
222
222
  args: t.List[t.Any] = []
223
223
  if self.value != UNDEFINED:
224
224
  args.append(self.value)
piccolo/conf/apps.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import ast
3
4
  import inspect
4
5
  import itertools
5
6
  import os
@@ -11,6 +12,8 @@ from dataclasses import dataclass, field
11
12
  from importlib import import_module
12
13
  from types import ModuleType
13
14
 
15
+ import black
16
+
14
17
  from piccolo.apps.migrations.auto.migration_manager import MigrationManager
15
18
  from piccolo.engine.base import Engine
16
19
  from piccolo.table import Table
@@ -32,8 +35,18 @@ class PiccoloAppModule(ModuleType):
32
35
  APP_CONFIG: AppConfig
33
36
 
34
37
 
38
+ def get_package(name: str) -> str:
39
+ """
40
+ :param name:
41
+ The __name__ variable from a Python file.
42
+
43
+ """
44
+ return ".".join(name.split(".")[:-1])
45
+
46
+
35
47
  def table_finder(
36
48
  modules: t.Sequence[str],
49
+ package: t.Optional[str] = None,
37
50
  include_tags: t.Optional[t.Sequence[str]] = None,
38
51
  exclude_tags: t.Optional[t.Sequence[str]] = None,
39
52
  exclude_imported: bool = False,
@@ -46,8 +59,10 @@ def table_finder(
46
59
 
47
60
  :param modules:
48
61
  The module paths to check for ``Table`` subclasses. For example,
49
- ``['blog.tables']``. The path should be from the root of your project,
50
- not a relative path.
62
+ ``['blog.tables']``.
63
+ :param package:
64
+ This must be passed in if the modules are relative paths (e.g.
65
+ if ``modules=['.tables']`` then ``package='blog'``).
51
66
  :param include_tags:
52
67
  If the ``Table`` subclass has one of these tags, it will be
53
68
  imported. The special tag ``'__all__'`` will import all ``Table``
@@ -83,10 +98,19 @@ def table_finder(
83
98
  table_subclasses: t.List[t.Type[Table]] = []
84
99
 
85
100
  for module_path in modules:
101
+ full_module_path = (
102
+ ".".join([package, module_path.lstrip(".")])
103
+ if package
104
+ else module_path
105
+ )
106
+
86
107
  try:
87
- module = import_module(module_path)
108
+ module = import_module(
109
+ module_path,
110
+ package=package,
111
+ )
88
112
  except ImportError as exception:
89
- print(f"Unable to import {module_path}")
113
+ print(f"Unable to import {full_module_path}")
90
114
  raise exception from exception
91
115
 
92
116
  object_names = [i for i in dir(module) if not i.startswith("_")]
@@ -100,7 +124,7 @@ def table_finder(
100
124
  ):
101
125
  table: Table = _object # type: ignore
102
126
 
103
- if exclude_imported and table.__module__ != module_path:
127
+ if exclude_imported and table.__module__ != full_module_path:
104
128
  continue
105
129
 
106
130
  if exclude_tags and set(table._meta.tags).intersection(
@@ -416,6 +440,17 @@ class Finder:
416
440
  else:
417
441
  return module
418
442
 
443
+ def get_piccolo_conf_path(self) -> str:
444
+ piccolo_conf_module = self.get_piccolo_conf_module()
445
+
446
+ if piccolo_conf_module is None:
447
+ raise ModuleNotFoundError("piccolo_conf.py not found.")
448
+
449
+ module_file_path = piccolo_conf_module.__file__
450
+ assert module_file_path
451
+
452
+ return module_file_path
453
+
419
454
  def get_app_registry(self) -> AppRegistry:
420
455
  """
421
456
  Returns the ``AppRegistry`` instance within piccolo_conf.
@@ -562,3 +597,88 @@ class Finder:
562
597
  tables.extend(app_config.table_classes)
563
598
 
564
599
  return tables
600
+
601
+
602
+ ###############################################################################
603
+
604
+
605
+ class PiccoloConfUpdater:
606
+
607
+ def __init__(self, piccolo_conf_path: t.Optional[str] = None):
608
+ """
609
+ :param piccolo_conf_path:
610
+ The path to the piccolo_conf.py (e.g. `./piccolo_conf.py`). If not
611
+ passed in, we use our ``Finder`` class to get it.
612
+ """
613
+ self.piccolo_conf_path = (
614
+ piccolo_conf_path or Finder().get_piccolo_conf_path()
615
+ )
616
+
617
+ def _modify_app_registry_src(self, src: str, app_module: str) -> str:
618
+ """
619
+ :param src:
620
+ The contents of the ``piccolo_conf.py`` file.
621
+ :param app_module:
622
+ The app to add to the registry e.g. ``'music.piccolo_app'``.
623
+ :returns:
624
+ Updated Python source code string.
625
+
626
+ """
627
+ ast_root = ast.parse(src)
628
+
629
+ parsing_successful = False
630
+
631
+ for node in ast.walk(ast_root):
632
+ if isinstance(node, ast.Call):
633
+ if (
634
+ isinstance(node.func, ast.Name)
635
+ and node.func.id == "AppRegistry"
636
+ ):
637
+ if len(node.keywords) > 0:
638
+ keyword = node.keywords[0]
639
+ if keyword.arg == "apps":
640
+ apps = keyword.value
641
+ if isinstance(apps, ast.List):
642
+ apps.elts.append(
643
+ ast.Constant(app_module, kind="str")
644
+ )
645
+ parsing_successful = True
646
+ break
647
+
648
+ if not parsing_successful:
649
+ raise SyntaxError(
650
+ "Unable to parse piccolo_conf.py - `AppRegistry(apps=...)` "
651
+ "not found)."
652
+ )
653
+
654
+ new_contents = ast.unparse(ast_root)
655
+
656
+ formatted_contents = black.format_str(
657
+ new_contents, mode=black.FileMode(line_length=80)
658
+ )
659
+
660
+ return formatted_contents
661
+
662
+ def register_app(self, app_module: str):
663
+ """
664
+ Adds the given app to the ``AppRegistry`` in ``piccolo_conf.py``.
665
+
666
+ This is used by command line tools like:
667
+
668
+ .. code-block:: bash
669
+
670
+ piccolo app new my_app --register
671
+
672
+ :param app_module:
673
+ The module of the app, e.g. ``'music.piccolo_app'``.
674
+
675
+ """
676
+ with open(self.piccolo_conf_path) as f:
677
+ piccolo_conf_src = f.read()
678
+
679
+ new_contents = self._modify_app_registry_src(
680
+ src=piccolo_conf_src, app_module=app_module
681
+ )
682
+
683
+ with open(self.piccolo_conf_path, "wt") as f:
684
+ f.write(new_contents)
@@ -61,7 +61,7 @@ class Delete(Query):
61
61
  querystring = QueryString(
62
62
  "{} WHERE {}",
63
63
  querystring,
64
- self.where_delegate._where.querystring,
64
+ self.where_delegate._where.querystring_for_update_and_delete,
65
65
  )
66
66
 
67
67
  if self.returning_delegate._returning:
@@ -104,7 +104,7 @@ class Update(Query[TableInstance, t.List[t.Any]]):
104
104
  querystring = QueryString(
105
105
  "{} WHERE {}",
106
106
  querystring,
107
- self.where_delegate._where.querystring_for_update,
107
+ self.where_delegate._where.querystring_for_update_and_delete,
108
108
  )
109
109
 
110
110
  if self.returning_delegate._returning:
piccolo/table.py CHANGED
@@ -573,6 +573,8 @@ class Table(metaclass=TableMetaclass):
573
573
 
574
574
  setattr(self, self._meta.primary_key._meta.name, None)
575
575
 
576
+ self._exists_in_db = False
577
+
576
578
  return self.__class__.delete().where(
577
579
  self.__class__._meta.primary_key == primary_key_value
578
580
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: piccolo
3
- Version: 1.24.1
3
+ Version: 1.25.0
4
4
  Summary: A fast, user friendly ORM and query builder which supports asyncio.
5
5
  Home-page: https://github.com/piccolo-orm/piccolo
6
6
  Author: Daniel Townsend
@@ -1,18 +1,18 @@
1
- piccolo/__init__.py,sha256=fqbFeUPVIVzt-2TqaZSPsISdBRr5iqLv8pFlnMYP1pU,23
1
+ piccolo/__init__.py,sha256=BvmUclKrZZEa9RXkZ0XNwqYKihj1aFvSDrl7jJkQmPc,23
2
2
  piccolo/custom_types.py,sha256=7HMQAze-5mieNLfbQ5QgbRQgR2abR7ol0qehv2SqROY,604
3
3
  piccolo/main.py,sha256=1VsFV67FWTUikPTysp64Fmgd9QBVa_9wcwKfwj2UCEA,5117
4
4
  piccolo/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  piccolo/querystring.py,sha256=kb7RYTvQZEyPsC4GH8vR2b_w35wnM-ita242S0_eyvQ,10013
6
6
  piccolo/schema.py,sha256=qNNy4tG_HqnXR9t3hHMgYXtGxHabwQAhUpc6RKLJ_gE,7960
7
- piccolo/table.py,sha256=UvEbagMYRkTbyFHTUwUshZlL_dC4UKDP7vUOwF8OXmg,50593
7
+ piccolo/table.py,sha256=vA4H5oebvpIsUYUpAIOFMqAG8wtNBWmu18zEmbfen0I,50629
8
8
  piccolo/table_reflection.py,sha256=02baOSLX6f2LEo0kruFZYF_nPPTbIvaCTH_KPGe0DKw,7540
9
9
  piccolo/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  piccolo/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  piccolo/apps/app/piccolo_app.py,sha256=8z2ITpxQQ-McxSYwQ5H_vyEnRXbY6cyAh2JSqhiylYk,340
12
12
  piccolo/apps/app/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- piccolo/apps/app/commands/new.py,sha256=odvFHBiKmbgmlfDXxMcU-0qlX8LumRdvPGzfX-SkMWI,2187
13
+ piccolo/apps/app/commands/new.py,sha256=XzcVBBHiEezIt0VvyNezLDSgcSRDn5rmefdVulReCSk,3382
14
14
  piccolo/apps/app/commands/show_all.py,sha256=46Hv3SipMT0YeMgAobU62O0mR7xvN1pn7xjU9Y2spKM,252
15
- piccolo/apps/app/commands/templates/piccolo_app.py.jinja,sha256=Wl11z0J0EiT5kDFJ_3Xps0dn-qndloDmAs1U_7UB1BY,554
15
+ piccolo/apps/app/commands/templates/piccolo_app.py.jinja,sha256=QjaBEQucryT-vCv37Qm47I2hdirGxYE725WUGqF2WR0,592
16
16
  piccolo/apps/app/commands/templates/tables.py.jinja,sha256=revzdrvDDwe78VedBKz0zYSwcsxyv2IURun6q6qmV1Y,32
17
17
  piccolo/apps/asgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  piccolo/apps/asgi/piccolo_app.py,sha256=7VUvqQJbB-ScO0A62S6MiJmQL9F5DS-SdlqlDLbAblE,217
@@ -20,7 +20,7 @@ piccolo/apps/asgi/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
20
20
  piccolo/apps/asgi/commands/new.py,sha256=718mXx7XdDTN0CKK0ZB1WVMkOrQtVfqT5bqO1kDKnRk,4335
21
21
  piccolo/apps/asgi/commands/templates/app/README.md.jinja,sha256=As3gNEZt9qcRmTVkjCzNtXJ8r4-3g0fCSe7Q-P39ezI,214
22
22
  piccolo/apps/asgi/commands/templates/app/_blacksheep_app.py.jinja,sha256=IKOql1G5wrEKm5qErlizOmrwYKlnxkm-d8NY5uVg9KA,3186
23
- piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja,sha256=nTzXc5IJLl_al1FuzG5AnaA1vSn-ipMurpPK7BibmB8,2710
23
+ piccolo/apps/asgi/commands/templates/app/_esmerald_app.py.jinja,sha256=fIbD106Us4j2DBv_41PimvFRVw2FuZhOfc3xFPkNjuY,2715
24
24
  piccolo/apps/asgi/commands/templates/app/_falcon_app.py.jinja,sha256=LOn3auJFeXNW48rtHzRbH3MzxWbRNhFib6Fm6wDS53E,1684
25
25
  piccolo/apps/asgi/commands/templates/app/_fastapi_app.py.jinja,sha256=mKnYfUOnYyWJA1jFoRLCUOGQlK6imaxx_1qaauGjeeQ,2627
26
26
  piccolo/apps/asgi/commands/templates/app/_lilya_app.py.jinja,sha256=PUph5Jj_AXVpxXZmpUzzHXogUchU8vjKBL_7WvgrfCU,1260
@@ -124,7 +124,7 @@ piccolo/columns/__init__.py,sha256=OYhO_n9anMiU9nL-K6ATq9FhAtm8RyMpqYQ7fTVbhxI,1
124
124
  piccolo/columns/base.py,sha256=_bg9yMWjMwE76Z7RDqi9iYSmtRuFx5bkx9uYJsFHKjQ,32487
125
125
  piccolo/columns/choices.py,sha256=-HNQuk9vMmVZIPZ5PMeXGTfr23o4nzKPSAkvcG1k0y8,723
126
126
  piccolo/columns/column_types.py,sha256=Wo6g14aL1vpOFugsY-6n-q6JUJaKih-cIn9NBp-f3fI,84759
127
- piccolo/columns/combination.py,sha256=vMXC2dfY7pvnCFhsT71XFVyb4gdQzfRsCMaiduu04Ss,6900
127
+ piccolo/columns/combination.py,sha256=NUOxmYcx84JW-2FcoF1XJVp_4R01aTJyl3waPzfZ4Tc,6955
128
128
  piccolo/columns/indexes.py,sha256=NfNok3v_791jgDlN28KmhP9ZCjl6031BXmjxV3ovXJk,372
129
129
  piccolo/columns/m2m.py,sha256=QMeSOnm4DT2cG9U5jC6sOZ6z9DxCWwDyZMSqk0wR2q4,14682
130
130
  piccolo/columns/readable.py,sha256=hganxUPfIK5ZXn-qgteBxsOJfBJucgr9U0QLsLFYcuI,1562
@@ -143,7 +143,7 @@ piccolo/columns/operators/comparison.py,sha256=G7bI_O-EXqun_zHwbNcZ9z9gsY8OK-0oB
143
143
  piccolo/columns/operators/math.py,sha256=knsUZzYOVdsFn3bTS0XC0ZzfNObeJcMvZ8Q_QwmGxjU,325
144
144
  piccolo/columns/operators/string.py,sha256=M5ifxHP-ttJaE_wYCl23W5sJof4i5S5_QDIOv34VxDM,142
145
145
  piccolo/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
- piccolo/conf/apps.py,sha256=AlgOM2ePl9NQjME5AQxcZ5RbuQpjRywFCj5uHUsCIZU,19040
146
+ piccolo/conf/apps.py,sha256=t5_Wn9A8_NRgSKmkkXqnq36yi-qucnpll41_XeX9nYE,22590
147
147
  piccolo/engine/__init__.py,sha256=Z0QR5NAA9jTFenY7pEJv1C8jZXBaFZojBUR3z3nx1cw,283
148
148
  piccolo/engine/base.py,sha256=4NISWRuvgp5ShqJbOEK6g8ok8Ijqtp2gzuM9J6o_wSU,6444
149
149
  piccolo/engine/cockroach.py,sha256=gGnihplotMZMWqHwRnZYnnbKU3jFrwttwOlNtktoeLE,1522
@@ -167,7 +167,7 @@ piccolo/query/methods/alter.py,sha256=AI9YkJeip2EitrWJN_TDExXhA8HGAG3XuDz1NR-Kir
167
167
  piccolo/query/methods/count.py,sha256=Vxn_7Ry-rleC6OGRxh-cLbuEMsy1DNjAZJThGED-_do,1748
168
168
  piccolo/query/methods/create.py,sha256=hJ-6VVsWczzKDH6fQRN1WmYhcitixuXJ-eNOuCo_JgM,2742
169
169
  piccolo/query/methods/create_index.py,sha256=gip_cRXZkLfpJqCL7KHk2l_7HLptoa-Ae8qu6I5d5c8,2224
170
- piccolo/query/methods/delete.py,sha256=3QNh8wsn2hUP1Ce9nz5ps1huU6ySHjyqkjdP-VYN-U8,2234
170
+ piccolo/query/methods/delete.py,sha256=X11IjaQ4kIPGDtYGVQBqbkm3MP6fDI2WuP4h1ri4zSQ,2256
171
171
  piccolo/query/methods/drop_index.py,sha256=5x3vHpoOmQ1SMhj6L7snKXX6M9l9j1E1PFSO6LMMkpY,1051
172
172
  piccolo/query/methods/exists.py,sha256=lTMjtrFPFygZmaPV3sfQKXc3K0sVqJ2S6PDc3fRK6YQ,1203
173
173
  piccolo/query/methods/indexes.py,sha256=J-QUqaBJwpgahskUH0Cu0Mq7zEKcfVAtDsUVIVX-C4c,943
@@ -177,7 +177,7 @@ piccolo/query/methods/raw.py,sha256=wQWR8b-yA_Gr-5lqRMZe9BOAAMBAw8CqTx37qVYvM1A,
177
177
  piccolo/query/methods/refresh.py,sha256=wg1zghKfwz-VmqK4uWa4GNMiDtK-skTqow591Hb3ONM,5854
178
178
  piccolo/query/methods/select.py,sha256=41OW-DIE_wr5VdxSusMKNT2aUhzQsCwK2Qh1XqgXHg0,22424
179
179
  piccolo/query/methods/table_exists.py,sha256=0yb3n6Jd2ovSBWlZ-gl00K4E7Jnbj7J8qAAX5d7hvNk,1259
180
- piccolo/query/methods/update.py,sha256=LfWqIXEl1aecc0rkVssTFmwyD6wXGhlKcTrUVhtlEsw,3705
180
+ piccolo/query/methods/update.py,sha256=KNrx5yzY3gshTNzXf392M7Aamz7TPZzZFHLhbDiEDu8,3716
181
181
  piccolo/query/operators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
182
182
  piccolo/query/operators/json.py,sha256=hdo1M6N9qTHJTJ0sRV9Bwt_iQZTgs4VdCKOPH1sXe-k,3168
183
183
  piccolo/testing/__init__.py,sha256=pRFSqRInfx95AakOq54atmvqoB-ue073q2aR8u8zR40,83
@@ -199,13 +199,13 @@ piccolo/utils/sync.py,sha256=j9Abkxn5HHS6HyvfpMzb1zV_teTkFHVhaIxu9rrSwSU,819
199
199
  piccolo/utils/warnings.py,sha256=ONrurw3HVCClUuHnpenMjg45dcFesrXqMgG9ifgP4_8,1247
200
200
  piccolo/utils/graphlib/__init__.py,sha256=SUJ5Yh7LiRun3nkBuLUSVmGNHF6fANrxSoYan0mtYB0,200
201
201
  piccolo/utils/graphlib/_graphlib.py,sha256=9FNGDSmTIEAk86FktniCe_J2yXjSE_sRZHDBAJJAUOw,9677
202
- piccolo-1.24.1.dist-info/licenses/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
202
+ piccolo-1.25.0.dist-info/licenses/LICENSE,sha256=zFIpi-16uIJ420UMIG75NU0JbDBykvrdnXcj5U_EYBI,1059
203
203
  profiling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
204
204
  profiling/run_profile.py,sha256=264qsSFu93NTpExePnKQ9GkcN5fiuRBQ72WOSt0ZHck,829
205
205
  tests/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
206
206
  tests/apps/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
207
207
  tests/apps/app/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
208
- tests/apps/app/commands/test_new.py,sha256=GxVAgyqEtbuHCUWaLhTzTUOxfqQ1daFq8SgdIcWjr_8,1187
208
+ tests/apps/app/commands/test_new.py,sha256=Z4Vt3G60sVw6Ab7npn3PS5daq5BTWEzlHu7LcZIorfg,2194
209
209
  tests/apps/app/commands/test_show_all.py,sha256=ca1afRhePYyVw-x2swxnoj659_qdu53uY8sVGIqBuNg,551
210
210
  tests/apps/asgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
211
211
  tests/apps/asgi/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -288,7 +288,7 @@ tests/columns/m2m/test_m2m.py,sha256=0ObmIHUJF6CZoNBznc5xXVr5_BbGBqOmWwtpg8IcPt4
288
288
  tests/columns/m2m/test_m2m_schema.py,sha256=oxu7eAjFFpDjnq9Eq-5OTNmlnsEIMFWx18OItfpVs-s,339
289
289
  tests/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
290
290
  tests/conf/example.py,sha256=K8sTttLpEac8rQlOLDY500IGkHj3P3NoyFbCMnT1EqY,347
291
- tests/conf/test_apps.py,sha256=Ovdp4v55iC-epS25sKntyYAw2ki9svcyCNOj5rOzE-E,8632
291
+ tests/conf/test_apps.py,sha256=5Cl9zL_nJr5NDS7KiTIIne219RGSouxP2gWhQK99F7A,9925
292
292
  tests/engine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
293
293
  tests/engine/test_extra_nodes.py,sha256=xW5gflHzM6ou26DqRSAZoaAbYVzF1IuMkW3vzNmB954,1298
294
294
  tests/engine/test_logging.py,sha256=VLf9A3QuoV7OhV8lttLDB4gzZemnG63kSr-Uyan005U,1287
@@ -335,7 +335,7 @@ tests/table/test_count.py,sha256=qm4dwlQJ5gv8FPSsgYTS-3Gsd_KLgvWlFnmXweKydxw,229
335
335
  tests/table/test_create.py,sha256=d-X7faDGI6NesvElu9JhrAo0Ialmrl6163Whd_LAUDQ,2887
336
336
  tests/table/test_create_db_tables.py,sha256=9ZVlv9jGX5uneMfM5c2j1LlOphgorFNHN1cQ1pay4gM,903
337
337
  tests/table/test_create_table_class.py,sha256=jOAxc38OhHBhdMzjsTOlYyzRjygj_5LXtsk46vmON_E,1745
338
- tests/table/test_delete.py,sha256=dUfGOz0p0OdwxtWhEH88OXL6zB5sd4ZyKvVmMs08T98,1252
338
+ tests/table/test_delete.py,sha256=NTUv3Dgo4cZp2kmIId4eBug3eW1IYVEtvs4AaQS14iA,1630
339
339
  tests/table/test_drop_db_tables.py,sha256=0a_aBZ8BMSLnu_DFXE_29X01B0jLdaa_WQ5_qTaZ5XY,1060
340
340
  tests/table/test_exists.py,sha256=AHvhodkRof7PVd4IDdGQ2nyOj_1Cag1Rpg1H84s4jU0,283
341
341
  tests/table/test_from_dict.py,sha256=I4PMxuzgkgi3-adaw9Gr3u5tQHexc31Vrq7RSPcPcJs,840
@@ -361,7 +361,7 @@ tests/table/instance/test_create.py,sha256=JD0l7L9dDK1FKPhUs6WC_B2bruPR1qQ8aIqXp
361
361
  tests/table/instance/test_get_related.py,sha256=eracFunh4Qlj5BEkI7OsrOyefRZM0rxrXnFX92VL1ZE,3285
362
362
  tests/table/instance/test_get_related_readable.py,sha256=QDMMZykxPsTWcsl8ZIZtmQVLwSGCw7QBilLepAAAnWg,4694
363
363
  tests/table/instance/test_instantiate.py,sha256=jvtaqSa_zN1lHQiykN4EnwitZqkWAbXle5IJtyhKuHY,958
364
- tests/table/instance/test_remove.py,sha256=Zv22ZZqot61rjCVWL1PHDf1oxELcBnmMXx1gsST6j80,648
364
+ tests/table/instance/test_remove.py,sha256=P-8wWodlA2PukdWhaj8x2HYZI1U8Q7oIuFBe2sAkcdo,757
365
365
  tests/table/instance/test_save.py,sha256=ccdDz-bR3aYDa16_RGQP7JTXprgm1mT6-NpF1y3RXyo,4388
366
366
  tests/table/instance/test_to_dict.py,sha256=gkiYkmcI5qcy5E-ERWWmO-Q8uyVSFfcpJ8d53LlzCuI,3442
367
367
  tests/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -380,8 +380,8 @@ tests/utils/test_sql_values.py,sha256=vzxRmy16FfLZPH-sAQexBvsF9MXB8n4smr14qoEOS5
380
380
  tests/utils/test_sync.py,sha256=9ytVo56y2vPQePvTeIi9lHIouEhWJbodl1TmzkGFrSo,799
381
381
  tests/utils/test_table_reflection.py,sha256=SIzuat-IpcVj1GCFyOWKShI8YkhdOPPFH7qVrvfyPNE,3794
382
382
  tests/utils/test_warnings.py,sha256=NvSC_cvJ6uZcwAGf1m-hLzETXCqprXELL8zg3TNLVMw,269
383
- piccolo-1.24.1.dist-info/METADATA,sha256=E-iRyVUdWPUJprZz1ofdSYkE9OPTLlCQAi_lguMXQlg,5531
384
- piccolo-1.24.1.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
385
- piccolo-1.24.1.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
386
- piccolo-1.24.1.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
387
- piccolo-1.24.1.dist-info/RECORD,,
383
+ piccolo-1.25.0.dist-info/METADATA,sha256=zdbTbYmUa-EhWuXfpt53Hr1RBENaNyiXBk80Ii_bFK8,5531
384
+ piccolo-1.25.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
385
+ piccolo-1.25.0.dist-info/entry_points.txt,sha256=SJPHET4Fi1bN5F3WqcKkv9SClK3_F1I7m4eQjk6AFh0,46
386
+ piccolo-1.25.0.dist-info/top_level.txt,sha256=-SR74VGbk43VoPy1HH-mHm97yoGukLK87HE5kdBW6qM,24
387
+ piccolo-1.25.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (77.0.3)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -3,7 +3,12 @@ import shutil
3
3
  import tempfile
4
4
  from unittest import TestCase
5
5
 
6
- from piccolo.apps.app.commands.new import module_exists, new
6
+ from piccolo.apps.app.commands.new import (
7
+ get_app_module,
8
+ module_exists,
9
+ new,
10
+ validate_app_name,
11
+ )
7
12
 
8
13
 
9
14
  class TestModuleExists(TestCase):
@@ -43,3 +48,37 @@ class TestNewApp(TestCase):
43
48
  "A module called sys already exists"
44
49
  )
45
50
  )
51
+
52
+
53
+ class TestValidateAppName(TestCase):
54
+
55
+ def test_validate_app_name(self):
56
+ """
57
+ Make sure only app names which work as valid Python package names are
58
+ allowed.
59
+ """
60
+ # Should be rejected:
61
+ for app_name in ("MY APP", "app/my_app", "my.app"):
62
+ with self.assertRaises(ValueError):
63
+ validate_app_name(app_name=app_name)
64
+
65
+ # Should work fine:
66
+ validate_app_name(app_name="music")
67
+
68
+
69
+ class TestGetAppIdentifier(TestCase):
70
+
71
+ def test_get_app_module(self):
72
+ """
73
+ Make sure the the ``root`` argument is handled correctly.
74
+ """
75
+ self.assertEqual(
76
+ get_app_module(app_name="music", root="."),
77
+ "music.piccolo_app",
78
+ )
79
+
80
+ for root in ("apps", "./apps", "./apps/"):
81
+ self.assertEqual(
82
+ get_app_module(app_name="music", root=root),
83
+ "apps.music.piccolo_app",
84
+ )
tests/conf/test_apps.py CHANGED
@@ -1,10 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import pathlib
4
+ import tempfile
4
5
  from unittest import TestCase
5
6
 
6
7
  from piccolo.apps.user.tables import BaseUser
7
- from piccolo.conf.apps import AppConfig, AppRegistry, Finder, table_finder
8
+ from piccolo.conf.apps import (
9
+ AppConfig,
10
+ AppRegistry,
11
+ Finder,
12
+ PiccoloConfUpdater,
13
+ table_finder,
14
+ )
8
15
  from tests.example_apps.mega.tables import MegaTable, SmallTable
9
16
  from tests.example_apps.music.tables import (
10
17
  Band,
@@ -310,3 +317,44 @@ class TestFinder(TestCase):
310
317
  self.assertListEqual(
311
318
  [i.app_name for i in sorted_app_configs], ["app_2", "app_1"]
312
319
  )
320
+
321
+
322
+ class TestPiccoloConfUpdater(TestCase):
323
+
324
+ def test_modify_app_registry_src(self):
325
+ """
326
+ Make sure the `piccolo_conf.py` source code can be modified
327
+ successfully.
328
+ """
329
+ updater = PiccoloConfUpdater()
330
+
331
+ new_src = updater._modify_app_registry_src(
332
+ src="APP_REGISTRY = AppRegistry(apps=[])",
333
+ app_module="music.piccolo_app",
334
+ )
335
+ self.assertEqual(
336
+ new_src.strip(),
337
+ 'APP_REGISTRY = AppRegistry(apps=["music.piccolo_app"])',
338
+ )
339
+
340
+ def test_register_app(self):
341
+ """
342
+ Make sure the new contents get written to disk.
343
+ """
344
+ temp_dir = tempfile.gettempdir()
345
+ piccolo_conf_path = pathlib.Path(temp_dir) / "piccolo_conf.py"
346
+
347
+ src = "APP_REGISTRY = AppRegistry(apps=[])"
348
+
349
+ with open(piccolo_conf_path, "wt") as f:
350
+ f.write(src)
351
+
352
+ updater = PiccoloConfUpdater(piccolo_conf_path=str(piccolo_conf_path))
353
+ updater.register_app(app_module="music.piccolo_app")
354
+
355
+ with open(piccolo_conf_path) as f:
356
+ contents = f.read().strip()
357
+
358
+ self.assertEqual(
359
+ contents, 'APP_REGISTRY = AppRegistry(apps=["music.piccolo_app"])'
360
+ )
@@ -17,9 +17,11 @@ class TestRemove(TestCase):
17
17
  "Maz"
18
18
  in Manager.select(Manager.name).output(as_list=True).run_sync()
19
19
  )
20
+ self.assertEqual(manager._exists_in_db, True)
20
21
 
21
22
  manager.remove().run_sync()
22
23
  self.assertTrue(
23
24
  "Maz"
24
25
  not in Manager.select(Manager.name).output(as_list=True).run_sync()
25
26
  )
27
+ self.assertEqual(manager._exists_in_db, False)
@@ -44,3 +44,18 @@ class TestDelete(DBTestCase):
44
44
  Band.delete().run_sync()
45
45
 
46
46
  Band.delete(force=True).run_sync()
47
+
48
+ def test_delete_with_joins(self):
49
+ """
50
+ Make sure delete works if the `where` clause specifies joins.
51
+ """
52
+
53
+ self.insert_rows()
54
+
55
+ Band.delete().where(Band.manager._.name == "Guido").run_sync()
56
+
57
+ response = (
58
+ Band.count().where(Band.manager._.name == "Guido").run_sync()
59
+ )
60
+
61
+ self.assertEqual(response, 0)