33"""
44from __future__ import annotations
55
6+ import decimal
67import inspect
78import itertools
89import json
@@ -349,7 +350,10 @@ class FormConfigResponseModel(BaseModel):
349350 description : t .Optional [str ] = None
350351
351352
352- @dataclass
353+ Number = t .Union [int , float , decimal .Decimal ]
354+ ChartData = t .Sequence [t .Tuple [str , Number ]]
355+
356+
353357class ChartConfig :
354358 """
355359 Used to specify charts, which are passed into ``create_admin``.
@@ -361,46 +365,58 @@ class ChartConfig:
361365 :param chart_type:
362366 Available chart types. There are five types: ``Pie``, ``Line``,
363367 ``Column``, ``Bar`` and ``Area``.
364- :param data:
365- The data to be passed to the admin ui. The data format must be
366- a ``list of lists`` (eg. ``[["Male", 7], ["Female", 3]]``).
368+ :param data_source:
369+ A function (async or sync) which returns the data to be displayed in
370+ the chart. It must returns a sequence of tuples. The first element is
371+ the label, and the second is the value::
372+
373+ >>> [("Male", 7), ("Female", 3)]
367374
368375 Here's a full example:
369376
370377 .. code-block:: python
371378
372- async def director_movie_count ():
379+ async def get_director_movie_count ():
373380 movies = await Movie.select(
374381 Movie.director.name.as_alias("director"),
375382 Count(Movie.id)
376383 ).group_by(
377384 Movie.director
378385 )
379386 # Flatten the response so it's a list of lists
380- # like [[ 'George Lucas', 3] , ...]
381- return [[ i['director'], i['count']] for i in movies]
387+ # like [( 'George Lucas', 3) , ...]
388+ return [( i['director'], i['count']) for i in movies]
382389
383390 director_chart = ChartConfig(
384391 title='Movie count',
385- chart_type="Pie", # or Bar or Line etc.
386- data=director_movie_count,
392+ chart_type="Pie", # or Bar or Line etc.
393+ data_source=get_director_movie_count
394+ )
387395
388396 create_admin(charts=[director_chart])
389397
390398 """
391399
392- def __init__ (self , title : str , chart_type : str , data : t .List [t .Any ]):
400+ def __init__ (
401+ self ,
402+ title : str ,
403+ data_source : t .Callable [[], t .Awaitable [ChartData ]],
404+ chart_type : t .Literal ["Pie" , "Line" , "Column" , "Bar" , "Area" ] = "Bar" ,
405+ ):
393406 self .title = title
394407 self .chart_slug = self .title .replace (" " , "-" ).lower ()
395408 self .chart_type = chart_type
396- self .data = data
409+ self .data_source = data_source
397410
398411
399- class ChartConfigResponseModel (BaseModel ):
412+ class ChartResponseModel (BaseModel ):
400413 title : str
401414 chart_slug : str
402415 chart_type : str
403- data : t .List [t .Any ]
416+
417+
418+ class ChartDataResponseModel (ChartResponseModel ):
419+ data : ChartData
404420
405421
406422def handle_auth_exception (request : Request , exc : Exception ):
@@ -440,7 +456,7 @@ class AdminRouter(FastAPI):
440456 def __init__ (
441457 self ,
442458 * tables : t .Union [t .Type [Table ], TableConfig ],
443- forms : t .List [FormConfig ] = [] ,
459+ forms : t .Optional [ t . List [FormConfig ]] = None ,
444460 auth_table : t .Type [BaseUser ] = BaseUser ,
445461 session_table : t .Type [SessionsBase ] = SessionsBase ,
446462 session_expiry : timedelta = timedelta (hours = 1 ),
@@ -452,11 +468,11 @@ def __init__(
452468 production : bool = False ,
453469 site_name : str = "Piccolo Admin" ,
454470 default_language_code : str = "auto" ,
455- translations : t .List [Translation ] = None ,
471+ translations : t .Optional [ t . List [Translation ] ] = None ,
456472 allowed_hosts : t .Sequence [str ] = [],
457473 debug : bool = False ,
458- charts : t .List [ChartConfig ] = [] ,
459- sidebar_links : t .Dict [str , str ] = {} ,
474+ charts : t .Optional [ t . List [ChartConfig ]] = None ,
475+ sidebar_links : t .Optional [ t . Dict [str , str ]] = None ,
460476 ) -> None :
461477 super ().__init__ (
462478 title = site_name ,
@@ -538,13 +554,13 @@ def __init__(
538554
539555 self .auth_table = auth_table
540556 self .site_name = site_name
541- self .forms = forms
557+ self .forms = forms or []
542558 self .read_only = read_only
543- self .charts = charts
559+ self .charts = charts or []
544560 self .chart_config_map = {
545561 chart .chart_slug : chart for chart in self .charts
546562 }
547- self .sidebar_links = sidebar_links
563+ self .sidebar_links = sidebar_links or {}
548564 self .form_config_map = {form .slug : form for form in self .forms }
549565
550566 with open (os .path .join (ASSET_PATH , "index.html" )) as f :
@@ -657,15 +673,15 @@ def __init__(
657673 endpoint = self .get_charts , # type: ignore
658674 methods = ["GET" ],
659675 tags = ["Charts" ],
660- response_model = t .List [ChartConfigResponseModel ],
676+ response_model = t .List [ChartResponseModel ],
661677 )
662678
663679 private_app .add_api_route (
664680 path = "/charts/{chart_slug:str}/" ,
665681 endpoint = self .get_single_chart , # type: ignore
666682 methods = ["GET" ],
667683 tags = ["Charts" ],
668- response_model = ChartConfigResponseModel ,
684+ response_model = ChartDataResponseModel ,
669685 )
670686
671687 private_app .add_api_route (
@@ -923,33 +939,35 @@ def get_user(self, request: Request) -> UserResponseModel:
923939 ###########################################################################
924940 # Custom charts
925941
926- def get_charts (self ) -> t .List [ChartConfigResponseModel ]:
942+ def get_charts (self ) -> t .List [ChartResponseModel ]:
927943 """
928944 Returns all charts registered with the admin.
929945 """
930946 return [
931- ChartConfigResponseModel (
947+ ChartResponseModel (
932948 title = chart .title ,
933949 chart_slug = chart .chart_slug ,
934950 chart_type = chart .chart_type ,
935- data = chart .data ,
936951 )
937952 for chart in self .charts
938953 ]
939954
940- def get_single_chart (self , chart_slug : str ) -> ChartConfigResponseModel :
955+ async def get_single_chart (
956+ self , chart_slug : str
957+ ) -> ChartDataResponseModel :
941958 """
942- Returns single chart.
959+ Returns a single chart.
943960 """
944961 chart = self .chart_config_map .get (chart_slug , None )
945962 if chart is None :
946963 raise HTTPException (status_code = 404 , detail = "No such chart found" )
947964 else :
948- return ChartConfigResponseModel (
965+ data = await chart .data_source ()
966+ return ChartDataResponseModel (
949967 title = chart .title ,
950968 chart_slug = chart .chart_slug ,
951969 chart_type = chart .chart_type ,
952- data = chart . data ,
970+ data = data ,
953971 )
954972
955973 ###########################################################################
@@ -1156,7 +1174,7 @@ def create_admin(
11561174 production : bool = False ,
11571175 site_name : str = "Piccolo Admin" ,
11581176 default_language_code : str = "auto" ,
1159- translations : t .List [Translation ] = None ,
1177+ translations : t .Optional [ t . List [Translation ] ] = None ,
11601178 auto_include_related : bool = True ,
11611179 allowed_hosts : t .Sequence [str ] = [],
11621180 debug : bool = False ,
0 commit comments