def resolve(self) -> t.Type[Table]: if self.app_name is not None: from piccolo.conf.apps import Finder finder = Finder() return finder.get_table_with_name( app_name=self.app_name, table_class_name=self.table_class_name) if self.module_path: module = importlib.import_module(self.module_path) table: t.Optional[t.Type[Table]] = getattr(module, self.table_class_name, None) from piccolo.table import Table if (table is not None and inspect.isclass(table) and issubclass(table, Table)): return table else: raise ValueError( f"Can't find a Table subclass called {self.app_name} " f"in {self.module_path}") raise ValueError("You must specify either app_name or module_path.")
def create_pydantic_fixture_model(fixture_configs: t.List[FixtureConfig]): """ Returns a nested Pydantic model for serialising and deserialising fixtures. """ columns: t.Dict[str, t.Any] = {} finder = Finder() for fixture_config in fixture_configs: app_columns: t.Dict[str, t.Any] = {} for table_class_name in fixture_config.table_class_names: table_class: t.Type[Table] = finder.get_table_with_name( app_name=fixture_config.app_name, table_class_name=table_class_name, ) app_columns[table_class_name] = ( t.List[ # type: ignore create_pydantic_model(table_class, include_default_columns=True)], ..., ) app_model: t.Any = pydantic.create_model( f"{fixture_config.app_name.title()}Model", **app_columns) columns[fixture_config.app_name] = (app_model, ...) model: t.Type[pydantic.BaseModel] = pydantic.create_model( "FixtureModel", **columns) return model
def main(): """ The entrypoint to the Piccolo CLI. """ # In case it's run from an entrypoint: sys.path.insert(0, os.getcwd()) ########################################################################### # Run in diagnose mode if requested. diagnose = get_diagnose_flag() if diagnose: print("Diagnosis...") if Finder(diagnose=True).get_app_registry(): print("Everything OK") return ########################################################################### cli = CLI(description="Piccolo CLI") ########################################################################### # Register the base apps. for _app_config in [ app_config, asgi_config, meta_config, migrations_config, playground_config, project_config, shell_config, sql_shell_config, user_config, ]: for command in _app_config.commands: cli.register(command, group_name=_app_config.app_name) ########################################################################### # Get user defined apps. try: APP_REGISTRY: AppRegistry = Finder().get_app_registry() except (ImportError, AttributeError): print("Can't import the APP_REGISTRY from piccolo_conf - some " "commands may be missing. If this is a new project don't worry. " f"To see a full traceback use `piccolo {DIAGNOSE_FLAG}`") else: for app_name, _app_config in APP_REGISTRY.app_configs.items(): for command in _app_config.commands: if cli.command_exists(group_name=app_name, command_name=command.__name__): # Skipping - already registered. continue cli.register(command, group_name=app_name) ########################################################################### cli.run()
def run(): """ Runs an iPython shell, and automatically imports all of the Table classes from your project. """ app_registry: AppRegistry = Finder().get_app_registry() tables = {} spacer = "-------" if app_registry.app_configs: print(spacer) for app_name, app_config in app_registry.app_configs.items(): app_config: AppConfig = app_config print(f"Importing {app_name} tables:") if app_config.table_classes: for table_class in sorted( app_config.table_classes, key=lambda x: x.__name__ ): table_class_name = table_class.__name__ print(f"- {table_class_name}") tables[table_class_name] = table_class else: print("- None") print(spacer) start_ipython_shell(**tables)
async def load_json_string(json_string: str): """ Parses the JSON string, and inserts the parsed data into the database. """ # We have to deserialise the JSON to find out which apps and tables it # contains, so we can create a Pydantic model. # Then we let Pydantic do the proper deserialisation, as it does a much # better job of deserialising dates, datetimes, bytes etc. deserialised_contents = load_json(json_string) app_names = deserialised_contents.keys() fixture_configs = [ FixtureConfig( app_name=app_name, table_class_names=[ i for i in deserialised_contents[app_name].keys() ], ) for app_name in app_names ] pydantic_model_class = create_pydantic_fixture_model( fixture_configs=fixture_configs) fixture_pydantic_model = pydantic_model_class.parse_raw(json_string) finder = Finder() engine = engine_finder() if not engine: raise Exception("Unable to find the engine.") async with engine.transaction(): for app_name in app_names: app_model = getattr(fixture_pydantic_model, app_name) for ( table_class_name, model_instance_list, ) in app_model.__dict__.items(): table_class = finder.get_table_with_name( app_name, table_class_name) await table_class.insert(*[ table_class.from_dict(row.__dict__) for row in model_instance_list ]).run()
def show_all(): """ Lists all registered Piccolo apps. """ app_registry = Finder().get_app_registry() print("Registered apps:") for app_path in app_registry.apps: print(app_path)
async def new(app_name: str, auto: bool = False): """ Creates a new migration file in the migrations folder. :param app_name: The app to create a migration for. :param auto: Auto create the migration contents. """ print("Creating new migration ...") engine = Finder().get_engine() if auto and isinstance(engine, SQLiteEngine): sys.exit("Auto migrations aren't currently supported by SQLite.") app_config = Finder().get_app_config(app_name=app_name) _create_migrations_folder(app_config.migrations_folder_path) await _create_new_migration(app_config=app_config, auto=auto)
def engine_finder(module_name: t.Optional[str] = None) -> t.Optional[Engine]: """ An example module name is `my_piccolo_conf`. The value used is determined by: module_name argument > environment variable > default. The module must be available on the path, so Python can import it. """ from piccolo.conf.apps import Finder return Finder().get_engine(module_name=module_name)
async def new( app_name: str, auto: bool = False, desc: str = "", auto_input: t.Optional[str] = None, ): """ Creates a new migration file in the migrations folder. :param app_name: The app to create a migration for. :param auto: Auto create the migration contents. :param desc: A description of what the migration does, for example --desc='adding name column'. :param auto_input: If provided, all prompts for user input will automatically have this entered. For example, --auto_input='y'. """ print("Creating new migration ...") engine = Finder().get_engine() if auto and isinstance(engine, SQLiteEngine): sys.exit("Auto migrations aren't currently supported by SQLite.") app_config = Finder().get_app_config(app_name=app_name) _create_migrations_folder(app_config.migrations_folder_path) try: await _create_new_migration( app_config=app_config, auto=auto, description=desc, auto_input=auto_input, ) except NoChanges: print("No changes detected - exiting.") sys.exit(0)
def parse_args(apps: str, tables: str) -> t.List[FixtureConfig]: """ Works out which apps and tables the user is referring to. """ finder = Finder() app_names = [] if apps == "all": app_names = finder.get_sorted_app_names() elif "," in apps: app_names = apps.split(",") else: # Must be a single app name app_names.append(apps) table_class_names: t.Optional[t.List[str]] = None if tables != "all": table_class_names = tables.split(",") if "," in tables else [tables] output: t.List[FixtureConfig] = [] for app_name in app_names: app_config = finder.get_app_config(app_name=app_name) table_classes = app_config.table_classes if table_class_names is None: fixture_configs = [i.__name__ for i in table_classes] else: fixture_configs = [ i.__name__ for i in table_classes if i.__name__ in table_class_names ] output.append( FixtureConfig( app_name=app_name, table_class_names=fixture_configs, )) return output
async def get_dump( fixture_configs: t.List[FixtureConfig], ) -> t.Dict[str, t.Any]: """ Gets the data for each table specified and returns a data structure like: .. code-block:: python { 'my_app_name': { 'MyTableName': [ { 'id': 1, 'my_column_name': 'foo' } ] } } """ finder = Finder() output: t.Dict[str, t.Any] = {} for fixture_config in fixture_configs: app_config = finder.get_app_config(app_name=fixture_config.app_name) table_classes = [ i for i in app_config.table_classes if i.__name__ in fixture_config.table_class_names ] sorted_table_classes = sort_table_classes(table_classes) output[fixture_config.app_name] = {} for table_class in sorted_table_classes: data = await table_class.select().run() output[fixture_config.app_name][table_class.__name__] = data return output
def test_get_table_classes(self): """ Make sure ``Table`` classes can be retrieved. """ finder = Finder() self.assertEqual( finder.get_table_classes(), [ Manager, Band, Venue, Concert, Ticket, Poster, Shirt, RecordingStudio, MegaTable, SmallTable, ], ) self.assertEqual( finder.get_table_classes(include_apps=["music"]), [ Manager, Band, Venue, Concert, Ticket, Poster, Shirt, RecordingStudio, ], ) self.assertEqual( finder.get_table_classes(exclude_apps=["music"]), [ MegaTable, SmallTable, ], ) with self.assertRaises(ValueError): # You shouldn't be allowed to specify both include and exclude. finder.get_table_classes(exclude_apps=["music"], include_apps=["mega"])
def main(): """ The entrypoint to the Piccolo CLI. """ # In case it's run from an entrypoint: sys.path.insert(0, os.getcwd()) ########################################################################### # Run in diagnose mode if requested. diagnose = get_diagnose_flag() if diagnose: print("Diagnosis...") if Finder(diagnose=True).get_app_registry(): print("Everything OK") return ########################################################################### cli = CLI(description="Piccolo CLI") ########################################################################### # Register the base apps. for _app_config in [ app_config, asgi_config, fixtures_config, meta_config, migrations_config, playground_config, project_config, schema_config, shell_config, sql_shell_config, tester_config, user_config, ]: for command in _app_config.commands: cli.register( command.callable, group_name=_app_config.app_name, aliases=command.aliases, ) ########################################################################### # Get user defined apps. try: APP_REGISTRY: AppRegistry = Finder().get_app_registry() except (ImportError, AttributeError): print("Can't import the APP_REGISTRY from piccolo_conf - some " "commands may be missing. If this is a new project don't worry. " f"To see a full traceback use `piccolo {DIAGNOSE_FLAG}`") else: for app_name, _app_config in APP_REGISTRY.app_configs.items(): for command in _app_config.commands: if cli.command_exists(group_name=app_name, command_name=command.callable.__name__): # Skipping - already registered. continue cli.register( command.callable, group_name=app_name, aliases=command.aliases, ) if "migrations" not in sys.argv: # Show a warning if any migrations haven't been run. # Don't run it if it looks like the user is running a migration # command, as this information is redundant. try: havent_ran_count = run_sync( CheckMigrationManager(app_name="all").havent_ran_count()) if havent_ran_count: message = (f"{havent_ran_count} migration hasn't" if havent_ran_count == 1 else f"{havent_ran_count} migrations haven't") colored_warning( message=("=> {} been run - the app " "might not behave as expected.\n" "To check which use:\n" " piccolo migrations check\n" "To run all migrations:\n" " piccolo migrations forwards all\n" ).format(message), level=Level.high, ) except Exception: pass ########################################################################### cli.run()
def graph( apps: str = "all", direction: str = "LR", output: t.Optional[str] = None ): """ Prints out a graphviz .dot file for your schema. :param apps: The name of the apps to include. If 'all' is given then every app is included. To specify multiple app names, separate them with commas. For example --apps="app1,app2". :param direction: How the tables should be orientated - by default it's "LR" which is left to right, so the graph will be landscape. The alternative is "TB", which is top to bottom, so the graph will be portrait. :param output: If specified, rather than printing out the file contents, they'll be written to this file. For example --output=graph.dot """ finder = Finder() app_names = finder.get_sorted_app_names() if apps != "all": given_app_names = [i.strip() for i in apps.split(",")] delta = set(given_app_names) - set(app_names) if delta: sys.exit(f"These apps aren't recognised: {', '.join(delta)}.") app_names = given_app_names tables: t.List[GraphTable] = [] relations: t.List[GraphRelation] = [] for app_name in app_names: app_config = finder.get_app_config(app_name=app_name) for table_class in app_config.table_classes: tables.append( GraphTable( name=table_class.__name__, columns=[ GraphColumn( name=i._meta.name, type=i.__class__.__name__ ) for i in table_class._meta.columns ], ) ) for fk_column in table_class._meta.foreign_key_columns: reference_table_class = ( fk_column._foreign_key_meta.resolved_references ) relations.append( GraphRelation( table_a=table_class.__name__, table_b=reference_table_class.__name__, label=fk_column._meta.name, ) ) contents = render_template( tables=tables, relations=relations, direction=direction ) if output is None: print(contents) else: with open(output, "w") as f: f.write(contents)