Giter Site home page Giter Site logo

lostindarkmath / pedantic-python-decorators Goto Github PK

View Code? Open in Web Editor NEW
23.0 2.0 2.0 1.18 MB

Some useful decorators for any situation. Includes runtime type checking.

License: Apache License 2.0

Python 99.51% Batchfile 0.12% Shell 0.37%
python decorators python-decorators pedantic pedantic-decorators type-checking annotations typechecker validation type-hints

pedantic-python-decorators's Introduction

pedantic-python-decorators Build Status Coverage Status PyPI version Conda Version Last Commit Stars Open Issues Open PRs

This packages includes many decorators that will make you write cleaner Python code.

Getting Started

This package requires Python 3.11 or later. There are multiple options for installing this package.

Option 1: Installing with pip from Pypi

Run pip install pedantic.

Option 2: Installing with conda from conda-forge

Run conda install -c conda-forge pedantic

Option 3: Installing with pip and git

  1. Install Git if you don't have it already.
  2. Run pip install git+https://github.com/LostInDarkMath/pedantic-python-decorators.git@master

Option 4: Offline installation using wheel

  1. Download the latest release here by clicking on pedantic-python-decorators-x.y.z-py-none-any.whl.
  2. Execute pip install pedantic-python-decorators-x.y.z-py3-none-any.whl.

The @pedantic decorator - Type checking at runtime

The @pedantic decorator does the following things:

  • The decorated function can only be called by using keyword arguments. Positional arguments are not accepted.
  • The decorated function must have type annotations.
  • Each time the decorated function is called, pedantic checks that the passed arguments and the return value of the function matches the given type annotations. As a consequence, the arguments are also checked for None, because None is only a valid argument, if it is annotated via typing.Optional.

In a nutshell: @pedantic raises an PedanticException if one of the following happened:

  • The decorated function is called with positional arguments.
  • The function has no type annotation for their return type or one or more parameters do not have type annotations.
  • A type annotation is incorrect.
  • A type annotation misses type arguments, e.g. typing.List instead of typing.List[int].

Minimal example

from pedantic import pedantic


@pedantic
def get_sum_of(values: list[int | float]) -> int:
    return sum(values)

get_sum_of(values=[0, 1.2, 3, 5.4])  # this raises the following runtime error: 
# Type hint of return value is incorrect: Expected type <class 'int'> but 10.0 of type <class 'float'> was the return value which does not match.

The @validate decorator

As the name suggests, with @validate you are able to validate the values that are passed to the decorated function. That is done in a highly customizable way. But the highest benefit of this decorator is that it makes it extremely easy to write decoupled easy testable, maintainable and scalable code. The following example shows the decoupled implementation of a configurable algorithm with the help of @validate:

import os
from dataclasses import dataclass

from pedantic import validate, ExternalParameter, overrides, Validator, Parameter, Min, ReturnAs


@dataclass(frozen=True)
class Configuration:
    iterations: int
    max_error: float


class ConfigurationValidator(Validator):
    @overrides(Validator)
    def validate(self, value: Configuration) -> Configuration:
        if value.iterations < 1 or value.max_error < 0:
            self.raise_exception(msg=f'Invalid configuration: {value}', value=value)

        return value


class ConfigFromEnvVar(ExternalParameter):
    """ Reads the configuration from environment variables. """

    @overrides(ExternalParameter)
    def has_value(self) -> bool:
        return 'iterations' in os.environ and 'max_error' in os.environ

    @overrides(ExternalParameter)
    def load_value(self) -> Configuration:
        return Configuration(
            iterations=int(os.environ['iterations']),
            max_error=float(os.environ['max_error']),
        )


class ConfigFromFile(ExternalParameter):
    """ Reads the configuration from a config file. """

    @overrides(ExternalParameter)
    def has_value(self) -> bool:
        return os.path.isfile('config.csv')

    @overrides(ExternalParameter)
    def load_value(self) -> Configuration:
        with open(file='config.csv', mode='r') as file:
            content = file.readlines()
            return Configuration(
                iterations=int(content[0].strip('\n')),
                max_error=float(content[1]),
            )


# choose your configuration source here:
@validate(ConfigFromEnvVar(name='config', validators=[ConfigurationValidator()]), strict=False, return_as=ReturnAs.KWARGS_WITH_NONE)
# @validate(ConfigFromFile(name='config', validators=[ConfigurationValidator()]), strict=False)

# with strict_mode = True (which is the default)
# you need to pass a Parameter for each parameter of the decorated function
# @validate(
#     Parameter(name='value', validators=[Min(5, include_boundary=False)]),
#     ConfigFromFile(name='config', validators=[ConfigurationValidator()]),
# )
def my_algorithm(value: float, config: Configuration) -> float:
    """
        This method calculates something that depends on the given value with considering the configuration.
        Note how well this small piece of code is designed:
            - Fhe function my_algorithm() need a Configuration but has no knowledge where this come from.
            - Furthermore, it doesn't care about parameter validation.
            - The ConfigurationValidator doesn't know anything about the creation of the data.
            - The @validate decorator is the only you need to change, if you want a different configuration source.
    """
    print(value)
    print(config)
    return value


if __name__ == '__main__':
    # we can call the function with a config like there is no decorator.
    # This makes testing extremely easy: no config files, no environment variables or stuff like that
    print(my_algorithm(value=2, config=Configuration(iterations=3, max_error=4.4)))

    os.environ['iterations'] = '12'
    os.environ['max_error'] = '3.1415'

    # but we also can omit the config and load it implicitly by our custom Parameters
    print(my_algorithm(value=42.0))

List of all decorators in this package

List of all mixins in this package

Dependencies

There are no hard dependencies. But if you want to use some advanced features you need to install the following packages:

  • multiprocess if you want to use the @in_subprocess decorator
  • flask if you want to you the request validators which are designed for Flask (see unit tests for examples):
    • FlaskParameter (abstract class)
    • FlaskJsonParameter
    • FlaskFormParameter
    • FlaskPathParameter
    • FlaskGetParameter
    • FlaskHeaderParameter
    • GenericFlaskDeserializer

Contributing

Feel free to contribute by submitting a pull request :)

Acknowledgments

Risks and side effects

The usage of decorators may affect the performance of your application. For this reason, I would highly recommend you to disable the decorators if your code runs in a productive environment. You can disable pedantic by set an environment variable:

export ENABLE_PEDANTIC=0

You can also disable or enable the environment variables in your project by calling a method:

from pedantic import enable_pedantic, disable_pedantic
enable_pedantic()

Issues with compiled Python code

This package is not compatible with compiled source code (e.g. with Nuitka). That's because it uses the inspect module from the standard library which will raise errors like OSError: could not get source code in case of compiled source code.

Don't forget to check out the documentation. Happy coding!

pedantic-python-decorators's People

Contributors

dependabot[bot] avatar lostindarkmath avatar mikemhenry avatar robwalt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pedantic-python-decorators's Issues

Problems when Union contains Callable Typehint

Example Code:

from datetime import datetime
from typing import Callable, Union

from pedantic import frozen_dataclass


@frozen_dataclass
class DatetimeCallable:
    dt: datetime | Callable[[], datetime]


if __name__ == '__main__':
    x = DatetimeCallable(
        dt=datetime(year=2022, month=1, day=1),
    ).validate_types()

Exception:

TypeError: datetime.datetime(2022, 1, 1, 0, 0) is not a callable object

coin flip TypeHinting-Errors at Generic Types

Hey there,
I upgradet to Pedantic 1.20 but came across a severe side effect error in pedantic.
All of my ~130 tests were successful in version<1.20 but now in 1.20 there are 4 failing tests.
All of them refer to this function of my stack-class:

 def top(self) -> Optional[T]:
        if len(self.items) > 0:
            return self.items[len(self.items)-1]
        else:
            return None

When the stack is empty, I want to return None, which I typehint with Optional.
Sometimes pedantic accepts this function and sometimes not! It depends on the order of my unit tests... Look at the two pictures:
pedantic_test1

If I put the failed test at first, other tests fail:
pedantic_test2

If I execute my test solely, everything is fine.
pedantic_test3

All of my tests are independent from each other. If I follow the debugger, sometimes pedantic throws an error and sometimes not - with everything else being equal.

Here is the full implementation of my stack class:

from typing import List, Optional, Union
from typing import TypeVar, Generic
from pedantic import pedantic_class

T = TypeVar('T')

@pedantic_class
class Stack(Generic[T]):
    def __init__(self) -> None:
        self.items: List[T] = []

    def push(self, item: T) -> None:
        self.items.append(item)

    def pop(self) -> T:
        return self.items.pop()

    def empty(self) -> bool:
        return not self.items

    def top(self) -> Optional[T]:
        if len(self.items) > 0:
            return self.items[len(self.items)-1]
        else:
            return None

So far, in all of every tests I instantiate a new stack object with an empty list of items. And I only put igraph.Vertex objects in it.

......
I spend some time in the debugger understanding this behaviour:
The first time I call stack.top() and let it return a None, everything is fine.
Second, third and n-th time returning a None will be fine.
But then the first time calling stack.top() when expecting to return a igraph.Vertex(), pedantic screams at me:

AssertionError: In function Stack.top:
E            For TypeVar ~T exists a type conflict: value None has type <class 'NoneType'> and value igraph.Vertex(<igraph.Graph object at 0x000001DDB10FBC70>, 0, {'name': 'open'}) has type <class 'igraph.Vertex'>

Pedantic expects a None.
So it looks like, your inner pedantic workings bind 'T' only to a None and not to None and the runtime-T-type.
Hope you can fix it.

import error in version 1.1.0

Hey there.

I upgraded the pedantic version to 1.1.0 but as soon I want to use it in my project, I get the error:

Traceback (most recent call last):
  File "...\src\main.py", line 3, in <module>
    from src.converter.converter import Converter
  File "...\src\converter\converter.py", line 5, in <module>
    from pedantic import pedantic_class
  File "...\venv38\lib\site-packages\pedantic\__init__.py", line 1, in <module>
    from pedantic.class_decorators \
  File "...\venv38\lib\site-packages\pedantic\class_decorators.py", line 4, in <module>
    from pedantic.method_decorators import pedantic, pedantic_require_docstring, trace, timer
  File "...\venv38\lib\site-packages\pedantic\method_decorators.py", line 11, in <module>
    from pedantic.models.decorated_function import DecoratedFunction
ModuleNotFoundError: No module named 'pedantic.models'

Seems like sth breaks inside you package!
Hope you can fix it soon

"Enhancement: forward refs" actually broke forward refs

After #69 was merged, forward refs are not parsed correctly anymore.
It complains that the type, which is forward referred to, is not defined.

Code:

@frozen_dataclass
class Comment:
    ...
    replies: List['Comment']

Error:
pedantic.exceptions.PedanticTypeCheckException: In dataclass "Comment" in field "replies": An error occurred during type hint checking. Value: [Comment(...)] Annotation: typing.List[ForwardRef('Comment')] Mostly this is caused by an incorrect type annotation. Details: name 'Comment' is not defined

Object based Validator

Currently every value has to be described in the decorator, this makes the definition of an request very long. Especially when the request is a post with a lot of data.

It would be very handy if there was a class based Serialization like in the django REST framework
Something like @validate(ObjectSerializer(MySerializerClass))

then the decorator returns the dataclass as a single parameter to the actual function.

Disable pedantic doesn't works as expected

Error message:

Traceback (most recent call last):
  File "C:/Users/WILLI/AppData/Roaming/JetBrains/PyCharm2020.3/scratches/bug.py", line 21, in <module>
    f.do()
  File "C:/Users/WILLI/AppData/Roaming/JetBrains/PyCharm2020.3/scratches/bug.py", line 12, in do
    print(self.bar(value=self._value))
TypeError: bar() got multiple values for argument 'value'

Example for reproducing the bug:

from pedantic import pedantic_class, disable_pedantic

disable_pedantic()


@pedantic_class
class Foo:
    def __init__(self) -> None:
        self._value = 42

    def do(self) -> None:
        print(self.bar(value=self._value))

    @staticmethod
    def bar(value: int) -> int:
        return value + 75


if __name__ == '__main__':
    f = Foo()
    f.do()

Optional fails at very long Docstring TypeHints

Hey there, closly related to #20 I have a similar problem in the same function:

Lets say I replace List[BPMNElement] with List[src.converter.bpmn_models.bpmn_element.BPMNElement]:

    def make_element(self, element_type: BPMNEnum,
                     src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
        """
        Searches all element_types in XML-DOM and returns corresponding
        BPMN-Objects.
        Args:
            element_type(BPMNEnum): abc
            **src_tgt_elements (Optional[List[src.converter.bpmn_models.bpmn_element.BPMNElement]]): abc**

        Returns:
            List[src.converter.bpmn_models.bpmn_element.BPMNElement]: abc
        """

Then I get the error:

AssertionError: In function GraphBuilder.make_element: Documented type of parameter src_tgt_elements is incorrect. Expected Union[List[src.converter.bpmn_models.bpmn_element.BPMNElement], NoneType] but documented is Optional[List[src.converter.bpmn_models.bpmn_element.BPMNElement]].`

The error message states, that Union[..., NoneType] should be used instead of Optional[...].
If I replace Optional with Union than everything is fine -- which is again syntactically awkward :(

pedantic fails at type hints of custom class inside custom class

Hey there,
I came across a small issue with the typeHint parser.

Consider the class Token has a function to compare itself to another token object:

class Token():
  @pedantic
  def compare(self, other: 'Token') -> bool
    # compares tokens. Returns True if equal, or false otherwise

Comparing two tokens:

tok1 = Token('abcd')
tok2 = Token('abcd')
if tok1.compare(other=tok2): # true, do something

I get the error message:

  File "...\lib\site-packages\pedantic\type_hint_parser.py", line 14, in is_instance
    if type_.__module__ == 'typing':
AttributeError: 'str' object has no attribute '__module__'

If I look inside the type hint parser I see the cause of the error:

if type_.__module__ == 'typing':

In this case you want to access the module-attribute of the string 'Token' which of course doesnt work.

It would be nice if you could fix the typehint parser in this special case.

move `Deserializable` out of flask_parameters

Deserializable is a simple abstract base class. Sure, it is used for Flask deserialization, but the base class itself does not depend on Flask.
Please move the class out of the Flask-dependent part of the pedantic, I don't want my package to depend on Flask, but still be used by a pedantic-using Flask service :)

Very long descriptive Docstring-TypeHint

Hey there,
I have a deep package structure in my project. When I want to add Docstring in my function I get a issues with the required syntax.

Issue: Full package name required - which can be very long.

 def make_element(self, element_type: BPMNEnum,
                     src_tgt_elements: Optional[List[BPMNElement]] = None) -> List[BPMNElement]:
        """
        Searches all element_types in XML-DOM and returns corresponding
        BPMN-Objects.
        Args:
            element_type(BPMNEnum): abc
            src_tgt_elements Optional[List[BPMNElement]]: abc

        Returns:
            **List[BPMNElement]: abc**
        """

In this case I get an error about the return type:

AssertionError: In function GraphBuilder.make_element:
 Documented type is incorrect: Annotation: List[src.converter.bpmn_models.bpmn_element.BPMNElement] Documented: List[BPMNElement]

Such long package names are very unhandy. Is is possible to just use 'BPMNElement' instead?

enable error messages to display function names

Heyho,
I tried to use your pedantic_class decorator on a class with alot of methods and documentation.
Somewhere in this class in a docstring syntax error. But I cant find it because the error messages doesnt show any function name or text snipped or line of code.

Example of a class containing two function. One with a correct docstring syntax and one without. But of course, in a larger class it is a miracle to find the syntax error - or you have to check every method with pedantic :(

from pedantic import pedantic_class
@pedantic_class
class Foo:
    def __init__(self, a: int) -> None:
        self.a = int
    def func(self, b:str) -> str:
        """
        Function with docstring syntax error below.
        Args:
            b (str):
            simple string
        Returns:
            str: simple string
        """
        return b
    def bunk(self) -> int:
        '''
        Function with correct docstring.
        Returns:
            int: 42
        '''
        return 42

if __name__ == '__main__':
    foo = Foo(a=10)
    foo.func(b='bar')

The error log is quite large. I only copy the featureless last lines:

File "...\lib\site-packages\docstring_parser\google.py", line 106, in _build_meta
    before, desc = text.split(":", 1)
ValueError: not enough values to unpack (expected 2, got 1)

Typechecks not working for ellipsis

Example Code:

from typing import Tuple

from pedantic import frozen_dataclass


@frozen_dataclass
class EllipsisClass:
    a: Tuple[...]


if __name__ == '__main__':
    x = EllipsisClass(a=(1, 2, 3)).validate_types()

Exception:

pedantic.exceptions.PedanticTypeCheckException: In dataclass "Data" in field "knowledge_items_to_map": An error occurred during type hint checking. Value: ('72d95a5e-bdc0-11e9-bd9f-0242ac120006', '317ca686-bf52-11e9-bd9f-0242ac120006', '584b61ba-bf54-11e9-bd9f-0242ac120006', '6033bfda-bf54-11e9-bd9f-0242ac120006', '6af40682-bf54-11e9-bd9f-0242ac120006', '056c72b0-bf25-11e9-bd9f-0242ac120006', 'f682bee0-c27a-11e9-92c7-0242ac120008') Annotation: typing.Tuple[...] Mostly this is caused by an incorrect type annotation. Details: 'ellipsis' object has no attribute '__module__' 

Establish Changelog

Every once in a while I see that this lib has a new release. Unfortunately the release notification doesn't contain any useful information besides the release date. As a interested person, I would love to know what changed, so maybe establishing a changelog would be helpful.

There are various ways to accomplish this. Here are two possible solutions:

  • creating and maintaining a manual changelog. As an example, we can take a look at this repo. Contributors are asked to manually write meaningful changelog entries and I think the latest section of the changelog is just copy-pasted in the body of the release form on the release date
  • adapting the conventional commit format and using tools like cocogitto (or others, there are many similar projects out there) to automatically create changelog information

pedantic fails at operator overload

Hey there,
I have an issue when decorating a function with @pedantic that overloads the python in-operator.
Example:

@pedantic
def __contains__(self, item:str) -> bool:
  if item in self.my_string:
    return True
  else:
    return False

I override contains to enable following syntax:

my_text = MyTextObject('abcd')
if 'ab' in my_text -> True # do something

But when I decorate contains with @pedantic and call it, then I get the error-message:

assert args_without_self == (), f'Use kwargs when you call {func.__name__}! {args_without_self}'
AssertionError: Use kwargs when you call __contains__! ('ab',)

I guess, because the python in-operator doesnt call contains with explicit function arguments? Is it a problem with your implementation or is it Python?

full code snippet:

from pedantic import pedantic
class MyTextObject:
    def __init__(self, text):
        self.my_string = text

    @pedantic
    def __contains__(self, item: str) -> bool:
        if item in self.my_string:
            return True
        else:
            return False

my_text = MyTextObject('abcd')
if 'ab' in my_text:
    print('yes, its working')

validate_args accesses custom class instead of arguments when decorated with pedantic

Heyho,

i came across a problem when decorating a function with validate_args and pedantic.
Instead of checking my function argument, validate_args accesses the instance of the class thus failing.

Example:
I want to check if the int ist greater then 0. I decorate this function with pedantic and call it:

from pedantic import validate_args, pedantic
class classA:
    def __init__(self, a: int) -> None:
        self.a = a

    @validate_args(lambda x: (x > 0,f'Argument should be greater then 0, but it was {x}.'))
    @pedantic
    def some_calculation(self, x: int) -> int:
        return x

if __name__ == '__main__':
    classA = classA(a=10)
    classA.some_calculation(x=15)

But I get the error:

Traceback (most recent call last):
  File ".../src/pypypy.py", line 17, in <module>
    classA.some_calculation(x=15)
  File "...\lib\site-packages\pedantic\method_decorators.py", line 289, in wrapper
    res, msg = validator(arg)
  File ".../src/pypypy.py", line 9, in <lambda>
    @validate_args(lambda x: (x > 0,f'Argument should be greater then 0, but it was {x}.'))
TypeError: '>' not supported between instances of 'classA' and 'int'

It seems like, validate_args wants to compare classA-object with an int which of course does not work.
If I switch the decorators, pedantic says:

File "...\lib\site-packages\pedantic\method_decorators.py", line 25, in __require_kwargs
    assert args_without_self == (), f'Use kwargs when you call {func.__name__}! {args_without_self}'
AssertionError: Use kwargs when you call some_calculation! (<__main__.classA object at 0x000001991322D8E0>,)

Hope you can enable double decorators like this! Would be really helpful! (:

Add support for Python 3.11

New typing features that comes with Python 3.11:

  • typing.Self
  • LiteralString: I will treat it as str, because pedantic does only type-checking at runtime and at runtime those cannot be distinguished
  • Never as alias for NoReturn
  • variadic generics:
    • TypeVarTuple:
      Ts = TypeVarTuple('Ts')
      
      class Array(Generic[*Ts]):
          ...
      
      x = Array[int, int, flat]()
    
      def add_dimension(a: Array[*Ts]) -> Array[int, *Ts]):
         ....

Pedantic shouldn't allow sloppy type annotations

Currently, things like the following are allowed.

@pedantic
def operation(l: list, d: dict) -> None
    # do something
```
But I think pedantic should force the usage of `typing.List` and `typing.Dict` instead.

Performance of inspect in pedantic package very low

Hihi,

I profiled my project & tests with CProfile and found out your pedantic package relies heavily on the inspect package. Which makes your pedantic decorators quite slow.
I added two pictures of the profiling:

It spends two full seconds in the inspect package with a total of 2800 calls! (left side of the graph)
getsource is called by three of your functions: is_property_setter, is_function_that_wants_args and is_static_method.
project profile

In my ~130 tests, inspect is called 20000(!!) times and spending 15 seconds in there. (bottom the graph)
tests profile

Hope you can add a little memoization in there!

pedantic fails at Optional TypeHints

Hey there,
the pedantic decorator seems to fail with optional arguments.
When executing the snippet below...

class MyClass():
    @pedantic
    def foo(self, a:int, b: Optional[int] = 1) -> int:
        return a+b

if __name__ == '__main__':
    myclass = MyClass()
    print(myclass.foo(a=10)) # should print 11

... pedantic prints:

Traceback (most recent call last):
  File ".../src/mypy.py", line 13, in <module>
    print(myclass.foo(a=10)) # should print 11
  File "...\lib\site-packages\pedantic\method_decorators.py", line 355, in wrapper
    return __require_kwargs_and_type_checking(func=func, args=args, kwargs=kwargs, annotations=annotations)
  File "...\lib\site-packages\pedantic\method_decorators.py", line 52, in __require_kwargs_and_type_checking
    assert len(kwargs) + 1 == len(annotations), \
AssertionError: Function "foo" misses some type hints or arguments: {'a': 10}, {'return': <class 'int'>, 'a': <class 'int'>, 'b': typing.Union[int, NoneType]}

Re-add Python 3.11 to CI

Due to an issue with a frozen job when using Python 3.11.0rc2+ I removed the 3.11-dev from the CI.

The release of Python 3.11 is planned for October 24, 2022. After this stable release, Python 3.11 should be re-added to the CI and the bug should be fixed if it is still present.

not a good error message with wrong Tuple-Type Hints

Heyhi,

when I put wrong type hints on a method, I get a not very useful error log.

from typing import Tuple
from pedantic import pedantic_class
@pedantic_class
class myStaticClass:

    @staticmethod
    def double_func(a:int) -> int:
        x,y = myStaticClass.static_bar()
        return x

    @staticmethod
    def static_bar() -> (int, int): #this is wrong. Correct would be Tuple[int, int]
        return 0, 1

if __name__ == '__main__':
    print(myStaticClass.double_func(a=0))

I get the error:

  File "...\lib\site-packages\pedantic\type_hint_parser.py", line 13, in is_instance
    if type_.__module__ == 'typing':
AttributeError: 'tuple' object has no attribute '__module__'

Not saying where the wrong type hint is. Hope you can fix it!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.